/******************************************************************************* * Copyright (c) 2007 Exadel, Inc. and Red Hat, Inc. * Distributed under license by Red Hat, Inc. All rights reserved. * This program is made available under the terms of the * Eclipse Public License v1.0 which accompanies this distribution, * and is available at http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Exadel, Inc. and Red Hat, Inc. - initial API and implementation ******************************************************************************/ package org.jboss.tools.common.model.impl; import java.util.*; import org.jboss.tools.common.model.*; import org.jboss.tools.common.model.impl.XModelImpl; /** * This is a class for holding children of model object * which may be sorted by a specific comparator. * @author glory */ public class RegularChildren { protected static XModelObject[] EMPTY = new XModelObject[0]; protected SMap objects = null; protected Comparator<XModelObject> comparator = null; public RegularChildren() {} /** * Returns false if children may be sorted. Returns true if order of * children is important (e.g. it is defined by storage). * * @return */ public boolean areChildrenOrdered() { return false; } /** * Removes all children. To save memory, object keeping children is set to * null. */ public void clear() { objects = null; } /** * Sets comparator to sort children if it is allowed. * * @param c */ public void setComparator(Comparator<XModelObject> c) { comparator = c; } /** * Returns true if there are no children. * * @return */ public boolean isEmpty() { return size() == 0; } /** * Returns the amount of children. * * @return */ public int size() { return (objects == null) ? 0 : objects.size(); } /** * Returns the amount of children for specified entity. * * @param entity * @return */ public int getChildrenCount(String entity) { return (objects == null) ? 0 : objects.getChildrenCount(entity); } /** * returns sorted values * * @return */ public XModelObject[] getObjects() { return (isEmpty()) ? EMPTY : objects.getSortedValues(comparator); } /** * Returns copy of object map, where keys are path parts of objects. * * @return */ public Map<String, XModelObject> getObjectsMap() { Map<String, XModelObject> result = new HashMap<String, XModelObject>(); if (objects != null) { Map<String, XModelObject> entries = objects.getMap(); synchronized(entries) { result.putAll(entries); } } return result; } /** * Returns object by key which should be equal to its path part. * * @param key * @return */ public XModelObject getObject(String key) { return (isEmpty()) ? null : objects.get(key); } /** * Adds child object. If a child with the same path part exists the * operation is rejected and false is returned. * * @param o * @return */ public boolean addObject(XModelObject o) { String pp = o.getPathPart(); if (pp == null) return false; if (objects != null && objects.get(pp) != null) { if (objects.get(pp) == o) return false; ((XModelObjectImpl) o).setParent_0(null); return false; } if (objects == null) objects = new SMap(); objects.put(pp, o); return true; } /** * Removes child object. To save memory, if children set becomes empty, * object keeping children is set to null. * * @param o * @return */ public boolean removeObject(XModelObject o) { if (objects == null) return false; String s = o.getPathPart(); if (objects.get(s) == null) return false; ((XModelObjectImpl) o).setParent_0(null); objects.remove(s); if (objects.size() == 0) objects = null; return true; } /** * Request for change of path part in specified child. If other object * exists that has proposed new path part, change is rejected and that * object is returned as cause for the failure. Otherwize, object is * re-registered in children map with new key. * * @param o * @param opp * @param npp * @return */ public XModelObject change(XModelObject o, String opp, String npp) { if (opp != null && opp.equals(npp)) return null; XModelObject c = getObject(npp); if (c != null && c != o) return c; if (objects == null) objects = new SMap(); if (opp != null) objects.remove(opp); objects.put(npp, o); if (opp != null && o.getParent() != null) { XModelImpl m = (XModelImpl) o.getModel(); m.fireStructureChanged(o.getParent()); } return null; } /** * Returns index of specified child in children array. If children can be * sorted, index is computed by sorted array. If child does not belong to * children, than -1 is returned. * * @param o * @return */ public int getIndex(XModelObject o) { XModelObject[] os = getObjects(); for (int i = 0; i < os.length; i++) if (os[i] == o) return i; return -1; } /** * Moves child from position 'from' to position 'to'. This method is not * relevant for children that may be sorted by comparator. Returns true if * change in the order of children did occure. * * @param from * @param to * @return */ public boolean move(int from, int to) { return false; } public void replaceChildren(XModelObject[] objects) { if(objects.length == 0) { if(this.objects != null) this.objects = null; } else { SMap m = new SMap(); for (int i = 0; i < objects.length; i++) { m.put(objects[i].getPathPart(), objects[i]); } this.objects = m; } } } /** * Keeps objects in a fast up-to-date map and in a sorted cache that is lazily * updated on requests. * * @author glory */ class SMap { Map<String,XModelObject> entries = new HashMap<String, XModelObject>(); XModelObject[] cache = RegularChildren.EMPTY; public int size() { return entries.size(); } public XModelObject get(String key) { return entries.get(key); } public void put(String key, XModelObject value) { synchronized(entries) { entries.put(key, value); } if(cache != null) { synchronized(this) { cache = null; } } } /** * Returns the stored map. * * @return */ public Map<String, XModelObject> getMap() { return entries; } private XModelObject[] values() { synchronized(entries) { return entries.values().toArray(RegularChildren.EMPTY); } } /** * Returns values of map sorted by the passed comparator. * @param comparator * @return */ public XModelObject[] getSortedValues(Comparator<XModelObject> comparator) { XModelObject[] c = cache; //Otherwise, cache can be made null between 'if' and 'return', but we //avoid synchronizing this line for the most probable return to be very fast. if (c != null) return c; synchronized (this) { if (cache != null) return cache; if (size() == 0) { cache = RegularChildren.EMPTY; } else { cache = values(); if (comparator != null) Arrays.sort(cache, comparator); } return cache; } } public void remove(String key) { synchronized(entries) { entries.remove(key); } if(cache != null) { synchronized(this) { cache = null; } } } /** * Returns the amount of objects for specified entity. * @param entity * @return */ public int getChildrenCount(String entity) { int k = 0; synchronized(entries) { for (XModelObject r : entries.values()) { String e = r.getModelEntity().getName(); if (entity.equals(e)) ++k; } } return k; } }