001: /* ====================================================================
002: Licensed to the Apache Software Foundation (ASF) under one or more
003: contributor license agreements. See the NOTICE file distributed with
004: this work for additional information regarding copyright ownership.
005: The ASF licenses this file to You under the Apache License, Version 2.0
006: (the "License"); you may not use this file except in compliance with
007: the License. You may obtain a copy of the License at
008:
009: http://www.apache.org/licenses/LICENSE-2.0
010:
011: Unless required by applicable law or agreed to in writing, software
012: distributed under the License is distributed on an "AS IS" BASIS,
013: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: See the License for the specific language governing permissions and
015: limitations under the License.
016: ==================================================================== */
017:
018: package org.apache.poi.hpsf;
019:
020: import java.util.Date;
021: import java.util.HashMap;
022: import java.util.Iterator;
023: import java.util.Map;
024:
025: import org.apache.poi.hpsf.wellknown.PropertyIDMap;
026:
027: /**
028: * <p>Maintains the instances of {@link CustomProperty} that belong to a
029: * {@link DocumentSummaryInformation}. The class maintains the names of the
030: * custom properties in a dictionary. It implements the {@link Map} interface
031: * and by this provides a simplified view on custom properties: A property's
032: * name is the key that maps to a typed value. This implementation hides
033: * property IDs from the developer and regards the property names as keys to
034: * typed values.</p>
035: *
036: * <p>While this class provides a simple API to custom properties, it ignores
037: * the fact that not names, but IDs are the real keys to properties. Under the
038: * hood this class maintains a 1:1 relationship between IDs and names. Therefore
039: * you should not use this class to process property sets with several IDs
040: * mapping to the same name or with properties without a name: the result will
041: * contain only a subset of the original properties. If you really need to deal
042: * such property sets, use HPSF's low-level access methods.</p>
043: *
044: * <p>An application can call the {@link #isPure} method to check whether a
045: * property set parsed by {@link CustomProperties} is still pure (i.e.
046: * unmodified) or whether one or more properties have been dropped.</p>
047: *
048: * <p>This class is not thread-safe; concurrent access to instances of this
049: * class must be syncronized.</p>
050: *
051: * @author Rainer Klute <a
052: * href="mailto:klute@rainer-klute.de"><klute@rainer-klute.de></a>
053: * @since 2006-02-09
054: * @version $Id$
055: */
056: public class CustomProperties extends HashMap {
057:
058: /**
059: * <p>Maps property IDs to property names.</p>
060: */
061: private Map dictionaryIDToName = new HashMap();
062:
063: /**
064: * <p>Maps property names to property IDs.</p>
065: */
066: private Map dictionaryNameToID = new HashMap();
067:
068: /**
069: * <p>Tells whether this object is pure or not.</p>
070: */
071: private boolean isPure = true;
072:
073: /**
074: * <p>Puts a {@link CustomProperty} into this map. It is assumed that the
075: * {@link CustomProperty} already has a valid ID. Otherwise use
076: * {@link #put(CustomProperty)}.</p>
077: */
078: public Object put(final Object name, final Object customProperty)
079: throws ClassCastException {
080: final CustomProperty cp = (CustomProperty) customProperty;
081: if (name == null) {
082: /* Ignoring a property without a name. */
083: isPure = false;
084: return null;
085: }
086: if (!(name instanceof String))
087: throw new ClassCastException(
088: "The name of a custom property must "
089: + "be a java.lang.String, but it is a "
090: + name.getClass().getName());
091: if (!(name.equals(cp.getName())))
092: throw new IllegalArgumentException("Parameter \"name\" ("
093: + name + ") and custom property's name ("
094: + cp.getName() + ") do not match.");
095:
096: /* Register name and ID in the dictionary. Mapping in both directions is possible. If there is already a */
097: final Long idKey = new Long(cp.getID());
098: final Object oldID = dictionaryNameToID.get(name);
099: dictionaryIDToName.remove(oldID);
100: dictionaryNameToID.put(name, idKey);
101: dictionaryIDToName.put(idKey, name);
102:
103: /* Put the custom property into this map. */
104: final Object oldCp = super .remove(oldID);
105: super .put(idKey, cp);
106: return oldCp;
107: }
108:
109: /**
110: * <p>Puts a {@link CustomProperty} that has not yet a valid ID into this
111: * map. The method will allocate a suitable ID for the custom property:</p>
112: *
113: * <ul>
114: *
115: * <li><p>If there is already a property with the same name, take the ID
116: * of that property.</p></li>
117: *
118: * <li><p>Otherwise find the highest ID and use its value plus one.</p></li>
119: *
120: * </ul>
121: *
122: * @param customProperty
123: * @return If the was already a property with the same name, the
124: * @throws ClassCastException
125: */
126: private Object put(final CustomProperty customProperty)
127: throws ClassCastException {
128: final String name = customProperty.getName();
129:
130: /* Check whether a property with this name is in the map already. */
131: final Long oldId = (Long) dictionaryNameToID.get(name);
132: if (oldId != null)
133: customProperty.setID(oldId.longValue());
134: else {
135: long max = 1;
136: for (final Iterator i = dictionaryIDToName.keySet()
137: .iterator(); i.hasNext();) {
138: final long id = ((Long) i.next()).longValue();
139: if (id > max)
140: max = id;
141: }
142: customProperty.setID(max + 1);
143: }
144: return this .put(name, customProperty);
145: }
146:
147: /**
148: * <p>Removes a custom property.</p>
149: * @param name The name of the custom property to remove
150: * @return The removed property or <code>null</code> if the specified property was not found.
151: *
152: * @see java.util.HashSet#remove(java.lang.Object)
153: */
154: public Object remove(final String name) {
155: final Long id = (Long) dictionaryNameToID.get(name);
156: if (id == null)
157: return null;
158: dictionaryIDToName.remove(id);
159: dictionaryNameToID.remove(name);
160: return super .remove(id);
161: }
162:
163: /**
164: * <p>Adds a named string property.</p>
165: *
166: * @param name The property's name.
167: * @param value The property's value.
168: * @return the property that was stored under the specified name before, or
169: * <code>null</code> if there was no such property before.
170: */
171: public Object put(final String name, final String value) {
172: final MutableProperty p = new MutableProperty();
173: p.setID(-1);
174: p.setType(Variant.VT_LPWSTR);
175: p.setValue(value);
176: final CustomProperty cp = new CustomProperty(p, name);
177: return put(cp);
178: }
179:
180: /**
181: * <p>Adds a named long property.</p>
182: *
183: * @param name The property's name.
184: * @param value The property's value.
185: * @return the property that was stored under the specified name before, or
186: * <code>null</code> if there was no such property before.
187: */
188: public Object put(final String name, final Long value) {
189: final MutableProperty p = new MutableProperty();
190: p.setID(-1);
191: p.setType(Variant.VT_I8);
192: p.setValue(value);
193: final CustomProperty cp = new CustomProperty(p, name);
194: return put(cp);
195: }
196:
197: /**
198: * <p>Adds a named double property.</p>
199: *
200: * @param name The property's name.
201: * @param value The property's value.
202: * @return the property that was stored under the specified name before, or
203: * <code>null</code> if there was no such property before.
204: */
205: public Object put(final String name, final Double value) {
206: final MutableProperty p = new MutableProperty();
207: p.setID(-1);
208: p.setType(Variant.VT_R8);
209: p.setValue(value);
210: final CustomProperty cp = new CustomProperty(p, name);
211: return put(cp);
212: }
213:
214: /**
215: * <p>Adds a named integer property.</p>
216: *
217: * @param name The property's name.
218: * @param value The property's value.
219: * @return the property that was stored under the specified name before, or
220: * <code>null</code> if there was no such property before.
221: */
222: public Object put(final String name, final Integer value) {
223: final MutableProperty p = new MutableProperty();
224: p.setID(-1);
225: p.setType(Variant.VT_I4);
226: p.setValue(value);
227: final CustomProperty cp = new CustomProperty(p, name);
228: return put(cp);
229: }
230:
231: /**
232: * <p>Adds a named boolean property.</p>
233: *
234: * @param name The property's name.
235: * @param value The property's value.
236: * @return the property that was stored under the specified name before, or
237: * <code>null</code> if there was no such property before.
238: */
239: public Object put(final String name, final Boolean value) {
240: final MutableProperty p = new MutableProperty();
241: p.setID(-1);
242: p.setType(Variant.VT_BOOL);
243: p.setValue(value);
244: final CustomProperty cp = new CustomProperty(p, name);
245: return put(cp);
246: }
247:
248: /**
249: * <p>Gets a named value from the custom properties.</p>
250: *
251: * @param name the name of the value to get
252: * @return the value or <code>null</code> if a value with the specified
253: * name is not found in the custom properties.
254: */
255: public Object get(final String name) {
256: final Long id = (Long) dictionaryNameToID.get(name);
257: final CustomProperty cp = (CustomProperty) super .get(id);
258: return cp != null ? cp.getValue() : null;
259: }
260:
261: /**
262: * <p>Adds a named date property.</p>
263: *
264: * @param name The property's name.
265: * @param value The property's value.
266: * @return the property that was stored under the specified name before, or
267: * <code>null</code> if there was no such property before.
268: */
269: public Object put(final String name, final Date value) {
270: final MutableProperty p = new MutableProperty();
271: p.setID(-1);
272: p.setType(Variant.VT_FILETIME);
273: p.setValue(value);
274: final CustomProperty cp = new CustomProperty(p, name);
275: return put(cp);
276: }
277:
278: /**
279: * <p>Sets the codepage.</p>
280: *
281: * @param codepage the codepage
282: */
283: public void setCodepage(final int codepage) {
284: final MutableProperty p = new MutableProperty();
285: p.setID(PropertyIDMap.PID_CODEPAGE);
286: p.setType(Variant.VT_I2);
287: p.setValue(new Integer(codepage));
288: put(new CustomProperty(p));
289: }
290:
291: /**
292: * <p>Gets the dictionary which contains IDs and names of the named custom
293: * properties.
294: *
295: * @return the dictionary.
296: */
297: Map getDictionary() {
298: return dictionaryIDToName;
299: }
300:
301: /**
302: * <p>Gets the codepage.</p>
303: *
304: * @return the codepage or -1 if the codepage is undefined.
305: */
306: public int getCodepage() {
307: int codepage = -1;
308: for (final Iterator i = this .values().iterator(); codepage == -1
309: && i.hasNext();) {
310: final CustomProperty cp = (CustomProperty) i.next();
311: if (cp.getID() == PropertyIDMap.PID_CODEPAGE)
312: codepage = ((Integer) cp.getValue()).intValue();
313: }
314: return codepage;
315: }
316:
317: /**
318: * <p>Tells whether this {@link CustomProperties} instance is pure or one or
319: * more properties of the underlying low-level property set has been
320: * dropped.</p>
321: *
322: * @return <code>true</code> if the {@link CustomProperties} is pure, else
323: * <code>false</code>.
324: */
325: public boolean isPure() {
326: return isPure;
327: }
328:
329: /**
330: * <p>Sets the purity of the custom property set.</p>
331: *
332: * @param isPure the purity
333: */
334: public void setPure(final boolean isPure) {
335: this.isPure = isPure;
336: }
337:
338: }
|