Java tutorial
/* Copyright 2012 Werner Diwischek * * 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. * Please include a hint to this site to all the stuff you change or generate<br/> * http://www.action4java.org<br/> * date 10.3.2012<br/> * @author Werner Diwischek * @version 0.11 ======= */ package org.jtree.core; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; /** * JTreeMap try to adopt a <b>hashes of hashes implementation</b> like the perl language offers as elementary type * like the array [] specifier in java * <br/> * <br/> * Where perl defines e.g. <i>$phone=$address->{'country'}->{'city'}->{'person'}->{'phone'}</i> you can use * <i>String phone=address.get("country:city:person:phone")</i> in JTreeMap. The delimiter is free but ":" is used * as default. To scan all persons for city you just * call <i>List <String> persons = address.getList("country:city");</i> and to get a list of all entries of a person you just * enter <i>List <String> personDatas = address.getList("country:city:person");</i> * <br/> * <br/> * This simplifies the construction of complex data container without the need to define a bunch of classes. * This could be used for rapid prototyping, and you can create the classes afterwards, if they were reused. If not you can avoid * the every-shit-a-class-paradigm and the freedom to access everything by free defined dynamic names. * <br/> * <br/> * JTreeMap consists of a generic Hashmap together with a HashMap of Lists, which are used to define hierarchical subsets of the "big" hashmap. * <br/> * <br/> * Since JTreeMap is a java object there are some methods added to perlMap which exceed the ability of hashes of hashes in perl. * The best alternative name would be TreeMap, but unfortunately its already used by java itself. * <br/> * <br/> * The JTreeMap class accepts no nulls. * The JTreeMap class uses Lists so its guaranteed, that the order of the hash keys will be constant. * Furthermore it implements different order getters, so this will simplify scans through the operation * <br /> * <br /> * Since JTreeMap just uses basically two instances of HashMaps, * all things for performance for for the basic operations (get and put), * and iterations depend are the same as for for HashMaps, it depends on initial capacity and load factor. * For certain operation feel free to change those values. I've made no performance tests at the moment. ======= * * Please include a hint to this site to all the stuff you change or generate<br/> * http://www..org<br/> * date 10.3.2012<br/> * @author Werner Diwischek * @version 0.11 */ public class JTreeMap<T> { private static final Log logger = LogFactory.getLog(JTreeMap.class); private HashMap<String, T> valuesMap = new HashMap<String, T>(); private HashMap<String, List<String>> directoriesMap = new HashMap<String, List<String>>(); private boolean force = false; private String delimiter = null; private String pointer = null; private Pattern fieldPattern = null; private Pattern listPattern = null; private Pattern childPattern = null; /** * A empty constructor just inialize an empty root list entry */ public JTreeMap() { this(":"); } /** * A constructor with a self defined separator just inialize an empty root list entry * The separator could not be changed after the instanciation of a JTreeMap object, since it makes no sense */ public JTreeMap(String delimiter) { if (delimiter == null) { logger.warn("Delimiter could not be null using default :"); return; } if (delimiter.equals("")) { logger.warn("Delimiter could not be empty String using default : "); return; } this.delimiter = delimiter; this.pointer = delimiter; directoriesMap.put(this.delimiter, new ArrayList<String>()); fieldPattern = Pattern.compile(delimiter); listPattern = Pattern.compile(delimiter + "[^" + delimiter + "]+$"); childPattern = Pattern.compile("(.*)" + delimiter); } public void setForce(boolean force) { this.force = force; } public String getDelimiter() { return this.delimiter; } // VALUE TYPE THINGS @Deprecated public T get(String key) { try { return read(this.getDirectoryCompatibility(key)); } catch (Exception e) { logger.error(e); } return null; } /** * Reads a value from key * @param key * @return */ public T read(String key) throws KeyNotFoundException, DirectoryExistsException { key = getDirectoryAbsolute(key); if (isListKey(key)) { throw new DirectoryExistsException("Key is a directory. Cant read value for " + key); } if (!isValueKey(key)) { throw new KeyNotFoundException("Key not found: " + key); } return this.valuesMap.get(key); } @Deprecated public T getPointer(String key) { try { return read(key); } catch (Exception e) { logger.error(e); } return null; } /** * Gets a whole hash under the key if its of leave type * @param key defines the name where to get the list to access the JTreeMap values * @param valuesMap defines leaf keys and values */ public HashMap<String, T> readMap(String key, boolean absoluteFlag) throws DirectoryNotFoundException, DirectoryNotEmptyException { if (!isListKey(key)) { throw new DirectoryNotFoundException("Could not find " + key); } logger.debug("Test" + key); List<String> listKeys = this.directoriesMap.get(key); HashMap<String, T> result = new HashMap<String, T>(); for (String setKey : listKeys) { String mapperKey = key + ":" + setKey; if (this.isValueKey(mapperKey)) { if (absoluteFlag) { result.put(mapperKey, valuesMap.get(mapperKey)); } else { result.put(setKey, valuesMap.get(mapperKey)); } } } return result; } @Deprecated public HashMap<String, T> getHashMap(String key) { try { return this.readMap(key, true); } catch (Exception e) { logger.error(e); } return null; } @Deprecated // Returns only the relative path public HashMap<String, T> getHashMapFlat(String key) { try { return this.readMap(key, false); } catch (Exception e) { logger.error(e); } return null; } /** * Inserts a single value * @param key * @return */ public void insert(String directory, String key, T value, boolean force) throws DirectoryNotFoundException, DirectoryNotEmptyException { if (value == null) { logger.warn("value for " + directory + " " + key + " is null -- doing nothing"); return; } if (directory == null) { logger.warn("directory is null"); return; } if (directory.equals("")) { logger.warn("directory is empty"); return; } if (key == null) { logger.warn("key is null"); return; } if (key.equals("")) { logger.warn("key is empty"); return; } directory = getDirectoryAbsolute(directory); if (!isListKey(directory)) { if (force) { this.mkdirs(directory); } else { throw new DirectoryNotFoundException("Key is a directory. Cant read value for " + key); } } String completeKey = directory + this.delimiter + key; if (!this.directoriesMap.get(directory).contains(key)) { this.directoriesMap.get(directory).add(key); } valuesMap.put(completeKey, value); } /** * Inserts a single value with no creation of directories * @param key * @return */ public void insert(String directory, String key, T value) throws DirectoryNotFoundException, DirectoryNotEmptyException { insert(directory, key, value, this.force); } /** * Compatibility - adapter * @param key * @param value * @throws DirectoryNotFoundException */ public void insert(String key, T value) throws DirectoryNotFoundException, DirectoryNotEmptyException { String parentDirectory = this.getParentDirectory(key); String childKey = this.getChildKey(key); logger.debug(parentDirectory + " --> " + childKey); insert(parentDirectory, childKey, value, this.force); } /** * Puts a whole hash under a key created by the pointer String and an Integer. * Returns the new created key. * If no entry found under the pointer, the key will be pointer:0 * If entries exist it will return pointer:list.size() * @param newKey defines the name where to get the list to access the JTreeMap values * @param newMap defines leaf keys and values * @return */ public void insert(String directory, List<T> entryList) throws DirectoryNotEmptyException, DirectoryNotFoundException { List<String> entries = this.directoriesMap.get(directory); int entryNum = 0; if (entries != null) { //List <String> entries = new List <String>(); entryNum = entries.size(); } for (T value : entryList) { entryNum++; this.insert(directory, new Integer(entryNum).toString(), value, this.force); } } @Deprecated public void put(String key, T value) { try { this.insert(this.getDirectoryCompatibility(key), value); } catch (Exception e) { logger.error(e); } } @Deprecated public void putPointer(String key, T value) { try { this.insert(key, value); } catch (Exception e) { logger.error(e); } } @Deprecated public void putPointer(Integer key, T value) { try { this.insert(key.toString(), value); } catch (Exception e) { logger.error(e); } } /** * Insert a whole hash under the key. Its the way to create a new list entry * @param directory defines the name where to get the list to access the JTreeMap values * @param values defines leaf keys and values */ public void insert(String directory, HashMap<String, T> values, boolean force) throws DirectoryNotFoundException, DirectoryNotEmptyException, DirectoryExistsException, KeyExistsException { if (values == null) { logger.warn("values for " + directory + " is null -- doing nothing"); return; } if (directory == null) { logger.warn("directory is null"); return; } if (directory.equals("")) { logger.warn("directory is empty"); return; } directory = getDirectoryAbsolute(directory); String parentDirectory = this.getParentDirectory(directory); if (!isListKey(parentDirectory)) { if (force) { this.mkdirs(parentDirectory); } else { throw new DirectoryNotFoundException( "Directory " + directory + " has no parent defined: " + parentDirectory); } } if (!isListKey(directory)) { this.mkdir(directory); } String localKey = ""; for (String key : values.keySet()) { this.insert(directory, key, values.get(key), force); } } /** * Inserts a HashMap with no creation of directories * @param key * @return */ public void insert(String directory, HashMap<String, T> values) throws DirectoryNotFoundException, DirectoryNotEmptyException, DirectoryExistsException, KeyExistsException { insert(directory, values, this.force); } @Deprecated public void put(String newKey, HashMap<String, T> newMap) { try { insert(this.getDirectoryCompatibility(newKey), newMap, true); } catch (Exception e) { logger.error(e); } } // DIRECTORY THINGS /** * Added for file system like method syntax * @param directory * @return */ public void mkdir(String directory) throws DirectoryNotFoundException, DirectoryExistsException, KeyExistsException { this.mkdir(directory, false); } /** * Adds directory * Create all child directories if not exits * @param directory * @return */ public void mkdir(String directory, boolean force) throws DirectoryNotFoundException, DirectoryExistsException, KeyExistsException { directory = getDirectoryAbsolute(directory); if (this.isValueKey(directory)) { throw new KeyExistsException("This key is already a value entry: " + directory); } else if (this.isListKey(directory)) { throw new DirectoryExistsException("This key is already a directory entry: " + directory); } String childKey = this.getChildKey(directory); String parentList = this.getParentDirectory(directory); logger.debug("ChildKey " + childKey + " from directory " + directory); if (!force) { if (!parentList.equals("")) { if (!this.isListKey(parentList)) { throw new DirectoryNotFoundException("The parentList " + parentList + " to create directory " + directory + " does not exist. Use -f option to create the whole directory!"); } } } else { if (!parentList.equals("")) { this.mkdirs(parentList); } } logger.debug("add " + directory + " directory: " + parentList + " " + childKey); this.directoriesMap.put(directory, new ArrayList<String>()); this.directoriesMap.get(parentList).add(childKey); } /** * Make all directories of the path * @param directory */ private void mkdirs(String directory) { if (directory == null) { logger.warn("Directory is null"); return; } if (directory.equals("")) { logger.warn("Directory is empty doing nothing"); return; } directory = directory.replaceAll("^" + this.delimiter, ""); String[] paths = directory.split(this.delimiter); String pathStep = ":"; String previousDir = ":"; for (String path : paths) { if (pathStep.equals(":")) {//first entry pathStep += path; } else { pathStep += ":" + path; } if (this.isListKey(pathStep)) { continue; } logger.debug("add " + pathStep + "directory: " + previousDir + " " + path); this.directoriesMap.put(pathStep, new ArrayList<String>()); this.directoriesMap.get(previousDir).add(path); previousDir = pathStep; } } @Deprecated private void createTree(String key) { try { mkdirs(key); } catch (Exception e) { logger.error(e); } } /** * Sets the pointer to allow "lazy" putter and setter * @param key * @return */ public void cd(String directory) throws DirectoryNotFoundException { if (directory == null) { logger.warn("directory is null"); return; } directory = getDirectoryAbsolute(directory); if (!isListKey(directory)) { throw new DirectoryNotFoundException("Key is a directory. Cant read value for "); } this.pointer = directory; } @Deprecated public void setPointer(String pointer) { logger.info("Pointer before " + this.pointer + " " + pointer); try { cd(this.getDirectoryCompatibility(pointer)); } catch (Exception e) { logger.error(e); } logger.info("Pointer afterwards " + this.pointer + " " + pointer); } @Deprecated public void addPointer(String pointer) { logger.info("Pointer before " + this.pointer + " " + pointer); try { cd(this.pointer + this.delimiter + pointer); } catch (Exception e) { logger.error(e); } logger.info("Pointer afterwards " + this.pointer + " " + pointer); } /** * Gets the actual pointer to allow "lazy" putter and setter * @param pointer * @return */ public String pwd() { return this.pointer; } @Deprecated public String getPointer() { return this.pwd(); } /** * Returns a List of keys defined by instance var pointer * @param directory * @return */ public List<String> ls() throws DirectoryNotFoundException, DirectoryNotEmptyException { return ls(this.pointer); } /** * Returns a List of keys defined by directory * @param directory * @return */ public List<String> ls(String directory) throws DirectoryNotFoundException, DirectoryNotEmptyException { return ls(directory, true); } /** * Returns a List of keys defined by directory * @param directory * @return */ public List<String> ls(String directory, boolean absoluteFlag) throws DirectoryNotFoundException, DirectoryNotEmptyException { if (directory == null) { throw new DirectoryNotFoundException("directory is null"); } String absoluteDirectory = getDirectoryAbsolute(directory); logger.debug("directory ls " + absoluteDirectory); if (this.isValueKey(absoluteDirectory)) { throw new DirectoryNotFoundException( "Key is a value not a directory. Use read to get the value " + absoluteDirectory); } if (!isListKey(absoluteDirectory)) { throw new DirectoryNotFoundException("Not a directory." + absoluteDirectory); } if (!absoluteFlag) { return (this.directoriesMap.get(absoluteDirectory)); } if (directory.startsWith(this.delimiter)) { List<String> myList = this.directoriesMap.get(absoluteDirectory); List<String> resultList = new ArrayList<String>(); for (String key : myList) { key = absoluteDirectory + this.delimiter + key; resultList.add(key); } return resultList; } return (this.directoriesMap.get(absoluteDirectory)); } @Deprecated public List<String> getMapList(String pointer) { try { return ls(this.getDirectoryCompatibility(pointer), false); } catch (Exception e) { logger.error(e); } return null; } @Deprecated public List<String> getMapListAbsolute(String pointer) { try { return ls(this.getDirectoryCompatibility(pointer), true); } catch (Exception e) { logger.error(e); } return null; } @Deprecated public List<String> getMapList() { try { return this.ls(this.pointer, false); } catch (Exception e) { logger.error(e); } return null; } // rmdir //================== /** * remove all entries of a data directory * @param directory * @return */ public void rmdir(String directory, boolean force) throws DirectoryNotFoundException, DirectoryNotEmptyException, DirectoryExistsException, KeyNotFoundException { if (directory == null) { logger.warn("directory is null"); return; } directory = getDirectoryAbsolute(directory); if (!isListKey(directory)) { throw new DirectoryNotFoundException("Key is a directory. Use rm to delete value for " + directory); } if (!force) { List<String> contentDir = ls(directory); if (contentDir.size() != 0) { throw new DirectoryNotEmptyException( "Directory is not empty " + directory + " with " + contentDir.size()); } } HashMap<String, List<String>> bufferMap = new HashMap<String, List<String>>(); // remove sublists for (String key : this.directoriesMap.keySet()) { if (key.startsWith(directory)) { String parentDirectory = this.getParentDirectory(key); if (isListKey(parentDirectory)) { String childKey = this.getChildKey(directory); bufferMap.put(parentDirectory, this.removeListEntry(parentDirectory, childKey)); } continue; } bufferMap.put(key, this.directoriesMap.get(key)); } this.directoriesMap = bufferMap; // remove remove entries HashMap<String, T> bufferValue = new HashMap<String, T>(); for (String key : this.valuesMap.keySet()) { if (key.startsWith(directory)) { String parentDirectory = this.getParentDirectory(key); if (isListKey(parentDirectory)) { String childKey = this.getChildKey(directory); bufferMap.put(parentDirectory, this.removeListEntry(parentDirectory, childKey)); } continue; } bufferValue.put(key, this.valuesMap.get(key)); } this.directoriesMap = bufferMap; this.valuesMap = bufferValue; } /** * remove all entries of a data directory * @param directory * @return */ public void rmdir(String directory) throws DirectoryNotFoundException, DirectoryNotEmptyException, DirectoryExistsException, KeyNotFoundException { rmdir(directory, this.force); } @Deprecated public void clearAllExceptParams() { try { rmdir(pointer); } catch (Exception e) { logger.error(e); } } @Deprecated public void clearPointer(String endPointer) { try { rmdir(pointer); } catch (Exception e) { logger.error(e); } } @Deprecated public void clear(String pointer) { try { rmdir(pointer); } catch (Exception e) { logger.error(e); } } /** * Removes a single value * @param key * @throws DirectoryExistsException * @throws KeyNotFoundException */ public void rm(String key) throws DirectoryExistsException, KeyNotFoundException, DirectoryNotFoundException { key = getDirectoryAbsolute(key); if (!this.isValueKey(key)) { throw new KeyNotFoundException("This key does not exist: " + key); } if (this.isListKey(key)) { throw new DirectoryExistsException("This key is already a directory entry: " + key); } String parentDirectory = this.getParentDirectory(key); if (this.isListKey(key)) { throw new DirectoryNotFoundException("Parentdirectory " + parentDirectory + " not found for " + key); } String childKey = this.getChildKey(key); this.valuesMap.remove(key); this.directoriesMap.put(parentDirectory, removeListEntry(parentDirectory, childKey)); } private List<String> removeListEntry(String parentDirectory, String childKey) { List<String> stripList = new ArrayList<String>(); for (String entry : this.directoriesMap.get(parentDirectory)) { if (entry.equals(childKey)) { continue; } stripList.add(entry); } return stripList; } /** * Reinit all values */ public void format() { valuesMap = new HashMap<String, T>(); directoriesMap = new HashMap<String, List<String>>(); } @Deprecated public void clearAll() { this.format(); } /** * Gets the whole hash stored in JTreeMap. Useful for extension to get access. * @param key defines the name where to get the list to access the JTreeMap values * @param valuesMap defines leaf keys and values */ public HashMap<String, T> getMap() { return this.valuesMap; } // Private checkers /** * Get the parent-list of a key * if key is list1:list2:list3 it will return list1:list2 * its private until I see some benefit to make it public * @param key * @return returns string before the last delimiter */ private String getParentDirectory(String key) { if (!key.contains(delimiter)) { return ":"; } Matcher match = listPattern.matcher(key); String parentDirectory = match.replaceFirst(""); if (parentDirectory.equals("")) { return this.delimiter; } return parentDirectory; } /** * Get the last sibling child of a key * if key is list1:list2:list3 it will return list3 * its private unil I see som benefit to make it public * @param key * @return the last entry after the last delimiter */ private String getChildKey(String key) { if (key == null) { logger.warn("key " + key + " is null?! "); return ""; } if (key.equals("")) { logger.warn("key " + key + " is empty?! "); return ""; } if (!key.contains(this.delimiter)) { logger.debug("key " + key + " is root! "); return ""; } Matcher match = childPattern.matcher(key); String childKey = match.replaceFirst(""); return childKey; } /** * Checks if a key is set for a value. * @param modelKey * @return */ public boolean isValueKey(String key) { if (this.valuesMap.get(key) != null) { return true; } return false; } /** * Checks if a key is set for a list. * @param modelKey * @return */ public boolean isListKey(String key) { if (this.directoriesMap.get(key) != null) { return true; } return false; } /** * Is the pointer defined for a value or a list * @return */ public boolean is(String key) { return (isListKey(key) || isValueKey(key)); } /** * Checks if the directory is absolute or not and returns absolute form * @param directory * @return */ private String getDirectoryAbsolute(String directory) { if (directory == null) { return this.pointer; } if (directory.startsWith(this.delimiter)) { return directory; } if (!this.pointer.equals(":")) { return this.pointer + this.delimiter + directory; } else { return this.pointer + directory; } } /** * Checks compatibility if the directory is absolute or not and returns absolute form * @param directory * @return */ private String getDirectoryCompatibility(String directory) { logger.info("set compatibility " + this.pointer + " --- " + directory); if (directory == null) { return this.pointer; } if (directory.startsWith(this.delimiter)) { logger.info("return absolute directory " + directory); return directory; } if (directory.contains(this.delimiter)) { return this.delimiter + directory; } if (!this.pointer.equals(":")) { return this.pointer + this.delimiter + directory; } else { return this.pointer + directory; } } /** * Checks the directory. Not used actually * @param key * @return */ private boolean checkDirectory(String key) { logger.debug("setMapListValue: " + key); String[] keys = fieldPattern.split(key); String thisKey = ""; String nextKey = ""; for (int i = 0; i < keys.length; i++) { logger.debug("Check tree value for " + i + " " + thisKey + " : " + nextKey + " "); if (thisKey.equals("")) { nextKey = keys[i]; } else { nextKey = thisKey + this.delimiter + keys[i]; } // Nothing defined at all --> create everything from the scratch if (this.isValueKey(thisKey)) { return false; } thisKey = nextKey; } return true; } }