Java tutorial
/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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 org.apache.cocoon.el.impl.objectmodel; import java.beans.PropertyDescriptor; import java.lang.reflect.Method; import java.util.HashMap; import java.util.Iterator; import java.util.ListIterator; import java.util.Map; import org.apache.cocoon.el.objectmodel.ObjectModel; import org.apache.cocoon.el.objectmodel.ObjectModelProvider; import org.apache.commons.collections.ArrayStack; import org.apache.commons.collections.KeyValue; import org.apache.commons.collections.MultiMap; import org.apache.commons.collections.iterators.ReverseListIterator; import org.apache.commons.collections.keyvalue.DefaultKeyValue; import org.apache.commons.collections.map.AbstractMapDecorator; import org.apache.commons.collections.map.MultiValueMap; import org.apache.commons.jxpath.DynamicPropertyHandler; import org.apache.commons.jxpath.JXPathBeanInfo; import org.apache.commons.jxpath.JXPathIntrospector; /** * Prototype implementation of {@link ObjectModel} interface. It <b>must</b> be initialized manually for now. * */ public class ObjectModelImpl extends AbstractMapDecorator implements ObjectModel { //FIXME: It seems that there is no easy way to reuse MuliValueMap private static final String SEGMENT_SEPARATOR = "/"; private ArrayStack localContexts; private Map singleValueMap; private MultiMap multiValueMap; private MultiMap multiValueMapForLocated; private Map initialEntries; //FIXME: This is a temporary solution private boolean modified; public ObjectModelImpl() { singleValueMap = new HashMap(); //FIXME: Not sure if this makes sense //super.map = UnmodifiableMap.decorate(singleValueMap); super.map = singleValueMap; localContexts = new ArrayStack(); multiValueMap = MultiValueMap.decorate(new HashMap(), StackReversedIteration.class); multiValueMapForLocated = MultiValueMap.decorate(new HashMap(), StackReversedIteration.class); } public static class StackReversedIteration extends ArrayStack { public Iterator iterator() { return new ReverseListIterator(this); } public ListIterator listIterator() { throw new UnsupportedOperationException(); } } public Object get(Object key) { //FIXME: This should be done more elegantly if ("this".equals(key)) { return this; } return super.get(key); } public MultiMap getAll() { return UnmodifiableMultiMap.decorate(multiValueMap); } public Object put(Object key, Object value) { modified = true; if (!localContexts.empty()) { ((ArrayStack) localContexts.peek()).push(new DefaultKeyValue(key, value)); } singleValueMap.put(key, value); multiValueMap.put(key, value); return value; } public void putAll(Map mapToCopy) { modified = true; if (!localContexts.empty()) { ArrayStack entries = (ArrayStack) localContexts.peek(); for (Iterator keysIterator = mapToCopy.keySet().iterator(); keysIterator.hasNext();) { Object key = keysIterator.next(); entries.push(new DefaultKeyValue(key, mapToCopy.get(key))); } } singleValueMap.putAll(mapToCopy); multiValueMap.putAll(mapToCopy); } /** * Locates map at given path * @param path where Map can be found * @param createIfNeeded indicates if map(s) should be created if no corresponding found * @return located Map or null if <code>createIfNeeded</code> is false and Map cannot be found */ private Map locateMapAt(String path, boolean createIfNeeded) { if (path.lastIndexOf(SEGMENT_SEPARATOR) == -1) { return this; } Map map = this; int segmentBegin = 0; int segmentEnd = path.indexOf(SEGMENT_SEPARATOR); while (segmentEnd != -1) { String key = path.substring(segmentBegin, segmentEnd); if (map.containsKey(key)) { Object obj = map.get(key); if (!(obj instanceof Map)) { throw new ClassCastException( "Object at path " + path.substring(0, segmentEnd) + "is not a Map"); } map = (Map) obj; } else { if (!createIfNeeded) { return null; } Map newMap = new HashMap(); map.put(key, newMap); map = newMap; } segmentBegin = segmentEnd + 1; segmentEnd = path.indexOf(SEGMENT_SEPARATOR, segmentBegin); } return map; } public void putAt(String path, Object value) { if (path == null) { throw new NullPointerException("Path cannot be null."); } if (path.length() == 0) { throw new IllegalArgumentException("Path cannot be empty"); } Map map = locateMapAt(path, true); String key = path.substring(path.lastIndexOf(SEGMENT_SEPARATOR) + 1, path.length()); if (!localContexts.empty()) { ((ArrayStack) localContexts.peek()).push(new PathValue(path, value)); } map.put(key, value); } private void removeAt(String path, Object value) { if (path == null) { throw new NullPointerException("Path cannot be null."); } if (path.length() == 0) { throw new IllegalArgumentException("Path cannot be empty"); } Map map = locateMapAt(path, false); String key = path.substring(path.lastIndexOf(SEGMENT_SEPARATOR) + 1, path.length()); if (map == null) { return; } multiValueMapForLocated.remove(key, value); if (multiValueMap.containsKey(key)) { map.put(key, ((StackReversedIteration) multiValueMap.get(key)).peek()); } else { map.remove(key); } } public void cleanupLocalContext() { if (localContexts.empty()) { throw new IllegalStateException("Local contexts stack is empty"); } ArrayStack removeEntries = (ArrayStack) localContexts.pop(); while (!removeEntries.isEmpty()) { if (removeEntries.peek() instanceof PathValue) { PathValue entry = (PathValue) removeEntries.pop(); removeAt(entry.getPath(), entry.getValue()); } else { KeyValue entry = (KeyValue) removeEntries.pop(); Object key = entry.getKey(); Object value = entry.getValue(); multiValueMap.remove(key, value); if (multiValueMap.containsKey(key)) { singleValueMap.put(key, ((StackReversedIteration) multiValueMap.get(key)).peek()); } else { singleValueMap.remove(key); } } } } public void markLocalContext() { localContexts.push(new ArrayStack()); } public Map getInitialEntries() { return initialEntries; } public void setInitialEntries(Map initialEntries) { if (this.initialEntries != null) { throw new IllegalStateException("Object Model has initial entries set already."); } this.initialEntries = initialEntries; for (Iterator keysIterator = initialEntries.keySet().iterator(); keysIterator.hasNext();) { Object key = keysIterator.next(); put(key, ((ObjectModelProvider) initialEntries.get(key)).getObject()); } this.modified = false; } public void fillContext() { // Hack: I use jxpath to populate the context object's properties // in the jexl context Object contextObject = get(CONTEXTBEAN); if (contextObject == null) { //nothing to do return; } // FIXME Exception Handling final JXPathBeanInfo bi = JXPathIntrospector.getBeanInfo(contextObject.getClass()); if (bi.isDynamic()) { Class cl = bi.getDynamicPropertyHandlerClass(); try { DynamicPropertyHandler h = (DynamicPropertyHandler) cl.newInstance(); String[] result = h.getPropertyNames(contextObject); int len = result.length; for (int i = 0; i < len; i++) { try { put(result[i], h.getProperty(contextObject, result[i])); } catch (Exception e) { e.printStackTrace(); } } } catch (Exception e) { e.printStackTrace(); } } else { PropertyDescriptor[] props = bi.getPropertyDescriptors(); int len = props.length; for (int i = 0; i < len; i++) { try { Method read = props[i].getReadMethod(); if (read != null) { put(props[i].getName(), read.invoke(contextObject, null)); } } catch (Exception e) { e.printStackTrace(); } } } } private final class PathValue { private String path; private Object value; public PathValue(String path, Object value) { this.path = path; this.value = value; } public String getPath() { return this.path; } public Object getValue() { return this.value; } } /* (non-Javadoc) * @see ObjectModel#setParent(ObjectModel) */ public void setParent(ObjectModel parentObjectModel) { if (this.modified) { throw new IllegalStateException("Setting parent may occur only if Object Model is empty."); } singleValueMap.putAll(parentObjectModel); multiValueMap.putAll(parentObjectModel.getAll()); } }