/* * Copyright 2002-2011 the original author or authors, or Red-Black IT Ltd, as appropriate. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.redblackit.version; import java.util.*; import org.codehaus.jackson.annotate.JsonIgnore; import org.codehaus.jackson.map.annotate.JsonDeserialize; /** * Implementation of composite version info based on maps. * * @author djnorth */ public class CompositeVersionInfoMap extends VersionInfoBase implements CompositeVersionInfo { /** * Comparator for our use */ private final ComponentKeyListComparator comparator = new ComponentKeyListComparator(); /** * Key concatenation separator */ private char keyConcatenationSeparator; /** * Map with version info */ private Map<String, CompositeVersionInfo> versionInfoMap; /** * Top level version strings */ private Map<String, String> versionStringMap; /** * Default constructor, using default key concatenation separator */ public CompositeVersionInfoMap() { this(CompositeVersionInfo.DEFAULT_KEY_CONCATENATION_SEPARATOR); } /** * Constructor taking key concatenation separator * * @param keyConcatenationSeparator */ public CompositeVersionInfoMap(char keyConcatenationSeparator) { super(); this.keyConcatenationSeparator = keyConcatenationSeparator; } /** * This returns a flattened map of version information, composing the keys * at this level with a specified separation character, using getVersionMap * from each VersionInfo. This means that, if a CompositeVersionInfo is * present, its own separator will be used for its getVersionMap. This gives * the power and responsibility to choose how separators are used through * the whole structure. * <p/> * If there is no version info, we return null. * * @return map as above. * @see com.redblackit.version.VersionInfo#getVersionMap() */ @Override @JsonIgnore public Map<String, String> getVersionMap() { Map<String, String> versionMap0 = null; if (versionStringMap == null) { versionMap0 = new TreeMap<String, String>(); } else { versionMap0 = getVersionStringMap(); } if (versionInfoMap != null) { for (String key0 : versionInfoMap.keySet()) { Map<String, String> versionMap1 = getVersionInfoForKey(key0) .getVersionMap(); for (String key1 : versionMap1.keySet()) { versionMap0.put(key0 + getKeyConcatenationSeparator() + key1, versionMap1.get(key1)); } } } return versionMap0; } /** * Get composite version info for specified keys, or null if not defined. * <p/> * The implementation should navigate down the specified composite version * info objects until: * <ul> * <li>it reaches last key in the sequence</li> * <li>there is no composite version info object for an intermediate key</li> * </ul> * * @param keys * @return compositeVersionInfo object for those keys, or null if none found * @see com.redblackit.version.CompositeVersionInfo#getVersionInfoForKeys(java.lang.String[]) */ @Override public CompositeVersionInfo getVersionInfoForKeys(String... keys) { CompositeVersionInfo lastVersionInfo = null; if (versionInfoMap != null && keys != null && keys.length > 0) { CompositeVersionInfo versionInfo = getVersionInfoForKey(keys[0]); if (keys.length == 1 || versionInfo == null) { lastVersionInfo = versionInfo; } else { lastVersionInfo = versionInfo.getVersionInfoForKeys(Arrays .copyOfRange(keys, 1, keys.length)); } } return lastVersionInfo; } /** * Set composite version info for keys, replacing any existing object. Any * required intermediate version info objects will be created, using our * separator character. * <p/> * A null or 0-length array, or a null key value, or null version info * object will cause a NullPointerException * * @param versionInfo * @param keys * @see CompositeVersionInfo#setVersionInfoForKeys(CompositeVersionInfo, String...) */ @Override public void setVersionInfoForKeys(CompositeVersionInfo versionInfo, String... keys) { if (keys == null || keys.length == 0 || keys[0] == null || versionInfo == null) { throw new NullPointerException("null or 0-length keys (=" + Arrays.toString(keys) + ") or null versionInfo (=" + versionInfo + ")"); } if (versionInfo == this) { throw new IllegalArgumentException("recursive call for key=" + keys + ":with versionInfo == this:" + this); } if (this.versionInfoMap == null) { this.versionInfoMap = new TreeMap<String, CompositeVersionInfo>(); } if (keys.length == 1) { this.versionInfoMap.put(keys[0], versionInfo); } else { CompositeVersionInfo nextCompositeVersionInfo = this.versionInfoMap .get(keys[0]); if (nextCompositeVersionInfo == null) { nextCompositeVersionInfo = new CompositeVersionInfoMap( keyConcatenationSeparator); this.versionInfoMap.put(keys[0], nextCompositeVersionInfo); } nextCompositeVersionInfo.setVersionInfoForKeys(versionInfo, Arrays.copyOfRange(keys, 1, keys.length)); } } /** * Get version string for specified keys, or null if not defined. * <p/> * Given key0, ...., keyN-1, keyN (assuming all objects are found), this is * equivalent to calling * <code>getVersionInfoForKeys(key0,..., keyN-1).getVersionForKey(keyN)</code> * * @param keys * @return version string for those keys, or null if none found * @see com.redblackit.version.CompositeVersionInfo#getVersionForKeys(java.lang.String[]) */ @Override public String getVersionForKeys(String... keys) { String version = null; if (keys != null && keys.length > 0) { if (keys.length == 1) { version = getVersionForKey(keys[0]); } else { CompositeVersionInfo containingVersionInfo = getVersionInfoForKeys(Arrays .copyOf(keys, keys.length - 1)); if (containingVersionInfo != null) { version = containingVersionInfo .getVersionForKeys(keys[keys.length - 1]); } } if (version == null && getLogger().isDebugEnabled()) { getLogger().debug( "getVersionForKeys:keys=" + Arrays.toString(keys) + ":no entry found"); } } return version; } /** * Set version string for specified keys, replacing any existing value. Any * required intermediate version info objects will be created, using our * separator character. * <p/> * A null or 0-length array, or a null key value, or null version string * object will cause a NullPointerException * * @param version * @param keys * @see com.redblackit.version.CompositeVersionInfo#setVersionForKeys(java.lang.String, * java.lang.String[]) */ @Override public void setVersionForKeys(String version, String... keys) { if (keys == null || keys.length == 0 || keys[0] == null || version == null) { throw new NullPointerException("null or 0-length keys (=" + Arrays.toString(keys) + ") or null version (=" + version + ")"); } if (keys.length == 1) { if (this.versionStringMap == null) { this.versionStringMap = new TreeMap<String, String>(); } this.versionStringMap.put(keys[0], version); } else { if (this.versionInfoMap == null) { this.versionInfoMap = new TreeMap<String, CompositeVersionInfo>(); } CompositeVersionInfo nextCompositeVersionInfo = this.versionInfoMap .get(keys[0]); if (nextCompositeVersionInfo == null) { nextCompositeVersionInfo = new CompositeVersionInfoMap( keyConcatenationSeparator); this.versionInfoMap.put(keys[0], nextCompositeVersionInfo); } nextCompositeVersionInfo.setVersionForKeys(version, Arrays.copyOfRange(keys, 1, keys.length)); } } /** * Return a copy of the structured map. * * @return versionInfoMap * @see com.redblackit.version.CompositeVersionInfo#getVersionInfoMap() */ @Override public Map<String, CompositeVersionInfo> getVersionInfoMap() { return (versionInfoMap == null ? null : new TreeMap<String, CompositeVersionInfo>(versionInfoMap)); } /** * Set complete version info map, clearing current map, and copying new * entries into a new map. This means that the source map need not be a * TreeMap. * <p/> * This method is suitable for use in Spring configuration. * * @param versionInfoMap to use */ @JsonDeserialize(contentAs = CompositeVersionInfoMap.class) public void setVersionInfoMap( Map<String, CompositeVersionInfo> versionInfoMap) { if (versionInfoMap == null) { this.versionInfoMap = null; } else { if (this.versionInfoMap == null) { this.versionInfoMap = new TreeMap<String, CompositeVersionInfo>(); } else { this.versionInfoMap.clear(); } this.versionInfoMap.putAll(versionInfoMap); } } /** * Set top-level version string map, clearing current map, and copying new * entries into a new map. This means that the source map need not be a * TreeMap. * <p/> * This method is suitable for use in Spring configuration. * * @param versionStringMap the versionStringMap to set */ public void setVersionStringMap(Map<String, String> versionStringMap) { if (versionStringMap == null) { this.versionStringMap = null; } else { if (this.versionStringMap == null) { this.versionStringMap = new TreeMap<String, String>(); } else { this.versionStringMap.clear(); } this.versionStringMap.putAll(versionStringMap); } } /** * Get top level version string map * * @return top-level version string map * @see com.redblackit.version.CompositeVersionInfo#getVersionStringMap() */ @Override public Map<String, String> getVersionStringMap() { return (versionStringMap == null ? null : new TreeMap<String, String>( versionStringMap)); } /** * Get top level version map with each single key a list of the component keys * * @return version string map * @see CompositeVersionInfo */ @Override @JsonIgnore public Map<List<String>, String> getVersionComponentMap() { Map<List<String>, String> versionComponentMap0 = new TreeMap<List<String>, String>(comparator); if (versionStringMap != null) { for (String key : versionStringMap.keySet()) { versionComponentMap0.put(Collections.singletonList(key), versionStringMap.get(key)); } } if (versionInfoMap != null) { for (String key : versionInfoMap.keySet()) { Map<List<String>, String> versionComponentMap1 = versionInfoMap.get(key).getVersionComponentMap(); for (List<String> componentKey1 : versionComponentMap1.keySet()) { List<String> componentKey0 = new ArrayList<String>(componentKey1); componentKey0.add(0, key); versionComponentMap0.put(componentKey0, versionComponentMap1.get(componentKey1)); } } } return versionComponentMap0; } /** * Get the maximum depth of the component version maps. * <ul><li>0 corresponds to the top level versionStringMap alone, or with versionInfoMap entries which are * themselves entirely empty of version information.</li> * <li>-1 implies no version information at all</li></ul> * * @return depth value * @see CompositeVersionInfo */ @Override @JsonIgnore public int getMaximumComponentVersionDepth() { int maxDepth = (versionStringMap == null ? -1 : 0); if (versionInfoMap != null) { for (String key : versionInfoMap.keySet()) { int componentDepth = (versionInfoMap.get(key).getMaximumComponentVersionDepth() + 1); if (componentDepth > maxDepth) { maxDepth = componentDepth; } } } return maxDepth; } /** * We can change the concatenation character * * @param keyConcatenationSeparator the keyConcatenationSeparator to set */ public void setKeyConcatenationSeparator(char keyConcatenationSeparator) { this.keyConcatenationSeparator = keyConcatenationSeparator; } /** * @return the keyConcatenationSeparator */ public char getKeyConcatenationSeparator() { return keyConcatenationSeparator; } /** * Normal toString * * @see java.lang.Object#toString() * */ @Override public String toString() { return toString(0); } /** * toString providing for indentation and so readability * * @param level * @return */ public String toString(int level) { char[] pfxcs = new char[level * 2]; Arrays.fill(pfxcs, ' '); String pfx = new String(pfxcs); StringBuilder builder = new StringBuilder(); builder.append("CompositeVersionInfoMap {keyConcatenationSeparator="); builder.append(keyConcatenationSeparator); builder.append("\n ").append(pfx).append("versionInfoMap="); if (versionInfoMap == null) { builder.append(versionInfoMap); } else { for (String key : versionInfoMap.keySet()) { builder.append("\n ").append(pfx).append("['").append(key).append("']:"); CompositeVersionInfo cvi = versionInfoMap.get(key); if (cvi instanceof CompositeVersionInfoMap) { builder.append(((CompositeVersionInfoMap) cvi).toString(level + 1)); } else { builder.append(cvi); } } } builder.append("\n ").append(pfx).append("versionStringMap="); builder.append(versionStringMap); builder.append('\n').append(pfx).append('}'); return builder.toString(); } /* * (non-Javadoc) * * @see java.lang.Object#hashCode() */ @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + keyConcatenationSeparator; result = prime * result + ((versionInfoMap == null) ? 0 : versionInfoMap.hashCode()); result = prime * result + ((versionStringMap == null) ? 0 : versionStringMap.hashCode()); return result; } /* * (non-Javadoc) * * @see java.lang.Object#equals(java.lang.Object) */ @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } CompositeVersionInfoMap other = (CompositeVersionInfoMap) obj; if (keyConcatenationSeparator != other.keyConcatenationSeparator) { return false; } if (versionInfoMap == null) { if (other.versionInfoMap != null) { return false; } } else if (!versionInfoMap.equals(other.versionInfoMap)) { return false; } if (versionStringMap == null) { if (other.versionStringMap != null) { return false; } } else if (!versionStringMap.equals(other.versionStringMap)) { return false; } return true; } /** * Return the CompositeVersionInfo for a specific key, or null if there is * none * * @param key * @return versionInfo */ protected CompositeVersionInfo getVersionInfoForKey(String key) { return (versionInfoMap == null || key == null ? null : versionInfoMap .get(key)); } /** * Get version string for supplied key, or null if not defined * * @param key * @return version string */ protected String getVersionForKey(String key) { return (versionStringMap == null || key == null ? null : versionStringMap.get(key)); } /** * Comparator for key lists */ public class ComponentKeyListComparator implements Comparator<List<String>> { /** * Compare using string value of lists * * @param keys0 * @param keys1 * @return comparison result * @see Comparator#compare(Object, Object) */ @Override public int compare(List<String> keys0, List<String> keys1) { return keys0.toString().compareTo(keys1.toString()); } } }