View Javadoc
1   /*-
2    * #%L
3    * io.earcam.instrumental.module.manifest
4    * %%
5    * Copyright (C) 2018 earcam
6    * %%
7    * SPDX-License-Identifier: (BSD-3-Clause OR EPL-1.0 OR Apache-2.0 OR MIT)
8    * 
9    * You <b>must</b> choose to accept, in full - any individual or combination of 
10   * the following licenses:
11   * <ul>
12   * 	<li><a href="https://opensource.org/licenses/BSD-3-Clause">BSD-3-Clause</a></li>
13   * 	<li><a href="https://www.eclipse.org/legal/epl-v10.html">EPL-1.0</a></li>
14   * 	<li><a href="https://www.apache.org/licenses/LICENSE-2.0">Apache-2.0</a></li>
15   * 	<li><a href="https://opensource.org/licenses/MIT">MIT</a></li>
16   * </ul>
17   * #L%
18   */
19  package io.earcam.instrumental.module.manifest;
20  
21  import java.io.OutputStream;
22  import java.util.HashMap;
23  import java.util.Map;
24  import java.util.Objects;
25  import java.util.Map.Entry;
26  import java.util.jar.Attributes;
27  import java.util.jar.Attributes.Name;
28  import java.util.jar.Manifest;
29  
30  import javax.annotation.WillNotClose;
31  
32  import io.earcam.unexceptional.Exceptional;
33  
34  /**
35   * <p>
36   * Abstract AbstractManifestBuilder class.
37   * </p>
38   *
39   */
40  public abstract class AbstractManifestBuilder<T extends ManifestInfoBuilder<T>> implements ManifestInfo, ManifestInfoBuilder<T> {
41  
42  	protected final Map<Name, CharSequence> mainAttributes = new HashMap<>();
43  	protected final Map<String, Map<Name, CharSequence>> namedEntries = new HashMap<>();
44  
45  	protected boolean hooked = false;
46  
47  
48  	/**
49  	 * <p>
50  	 * self.
51  	 * </p>
52  	 *
53  	 * @return a T object.
54  	 */
55  	protected abstract T self();
56  
57  
58  	@Override
59  	public boolean equals(Object other)
60  	{
61  		return other instanceof AbstractManifestBuilder && equals(AbstractManifestBuilder.class.cast(other));
62  	}
63  
64  
65  	/**
66  	 * <p>
67  	 * Implementers should use check own properties, invoke {@code instanceof} and then delegate to
68  	 * {@link #same(AbstractManifestBuilder)}
69  	 * </p>
70  	 *
71  	 * @param that an {@link AbstractManifestBuilder} instance.
72  	 * @see #same(AbstractManifestBuilder)
73  	 * @return a boolean.
74  	 */
75  	public abstract boolean equals(AbstractManifestBuilder<?> that);
76  
77  
78  	/**
79  	 * <p>
80  	 * same.
81  	 * </p>
82  	 *
83  	 * @param that a {@link AbstractManifestBuilder} object.
84  	 * @return a boolean.
85  	 */
86  	protected final boolean same(AbstractManifestBuilder<?> that)
87  	{
88  		return that != null
89  				&& Objects.equals(that.mainAttributes, mainAttributes)
90  				&& Objects.equals(that.namedEntries, namedEntries);
91  	}
92  
93  
94  	@Override
95  	public int hashCode()
96  	{
97  		return Objects.hash(mainAttributes, namedEntries);
98  	}
99  
100 
101 	@Override
102 	public T manifestMain(Entry<Name, ? extends CharSequence> attribute)
103 	{
104 		mainAttributes.put(attribute.getKey(), attribute.getValue());
105 		return self();
106 	}
107 
108 
109 	@Override
110 	public T manifestNamed(ManifestNamedEntry entry)
111 	{
112 		Map<Name, CharSequence> attributes = namedEntries.computeIfAbsent(entry.name(), n -> new HashMap<>());
113 		attributes.putAll(entry.attributes());
114 		return self();
115 	}
116 
117 
118 	@Override
119 	public T mergeFrom(Manifest manifest)
120 	{
121 		for(Object entry : manifest.getMainAttributes().entrySet()) {
122 			@SuppressWarnings("unchecked")
123 			Entry<Name, String> x = (Entry<Name, String>) entry;
124 			manifestMain(x);
125 		}
126 
127 		for(Map.Entry<String, Attributes> namedEntry : manifest.getEntries().entrySet()) {
128 			ManifestNamedEntry x = ManifestNamedEntry.entry(namedEntry.getKey());
129 			for(Entry<Object, Object> entry : namedEntry.getValue().entrySet()) {
130 				x.attribute((Name) entry.getKey(), (String) entry.getValue());
131 			}
132 			manifestNamed(x);
133 		}
134 		return self();
135 	}
136 
137 
138 	@Override
139 	public Manifest to(Manifest manifest)
140 	{
141 		hook();
142 		mainAttributes.entrySet().forEach(e -> manifest.getMainAttributes().putIfAbsent(e.getKey(), e.getValue().toString()));
143 
144 		Map<String, Attributes> entries = manifest.getEntries();
145 		for(Entry<String, Map<Name, CharSequence>> named : namedEntries.entrySet()) {
146 
147 			Attributes sink = entries.computeIfAbsent(named.getKey(), n -> new Attributes());
148 			for(Entry<Name, CharSequence> source : named.getValue().entrySet()) {
149 				sink.put(source.getKey(), source.getValue().toString());
150 			}
151 		}
152 		return manifest;
153 	}
154 
155 
156 	/**
157 	 * <p>
158 	 * hook.
159 	 * </p>
160 	 */
161 	protected final void hook()
162 	{
163 		if(!hooked) {
164 			preBuildHook();
165 			hooked = true;
166 			preFlightChecks();
167 		}
168 	}
169 
170 
171 	private void preFlightChecks()
172 	{
173 		if(!(mainAttributes.containsKey(Name.MANIFEST_VERSION) || mainAttributes.containsKey(Name.SIGNATURE_VERSION))) {
174 			mainAttributes.put(Name.MANIFEST_VERSION, "1.0");
175 		}
176 	}
177 
178 
179 	/**
180 	 * <p>
181 	 * preBuildHook.
182 	 * </p>
183 	 */
184 	protected abstract void preBuildHook();
185 
186 
187 	/**
188 	 * <p>
189 	 * unhook.
190 	 * </p>
191 	 */
192 	protected final void unhook()
193 	{
194 		hooked = false;
195 		// landingChecks
196 	}
197 
198 
199 	@Override
200 	public void to(@WillNotClose OutputStream out)
201 	{
202 		hook();
203 		Exceptional.accept(toManifest()::write, out);
204 	}
205 
206 
207 	/**
208 	 * <p>
209 	 * mainAttributes.
210 	 * </p>
211 	 *
212 	 * @return a {@link java.util.Map} object.
213 	 */
214 	public Map<Name, CharSequence> mainAttributes()
215 	{
216 		return mainAttributes;
217 	}
218 
219 
220 	/**
221 	 * <p>
222 	 * namedEntries.
223 	 * </p>
224 	 *
225 	 * @return a {@link java.util.Map} object.
226 	 */
227 	public Map<String, Map<Name, CharSequence>> namedEntries()
228 	{
229 		return namedEntries;
230 	}
231 }