/** * This file Copyright (c) 2005-2007 Aptana, Inc. This program is * dual-licensed under both the Aptana Public License and the GNU General * Public license. You may elect to use one or the other of these licenses. * * This program is distributed in the hope that it will be useful, but * AS-IS and WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, TITLE, or * NONINFRINGEMENT. Redistribution, except as permitted by whichever of * the GPL or APL you select, is prohibited. * * 1. For the GPL license (GPL), you can redistribute and/or modify this * program under the terms of the GNU General Public License, * Version 3, as published by the Free Software Foundation. You should * have received a copy of the GNU General Public License, Version 3 along * with this program; if not, write to the Free Software Foundation, Inc., 51 * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Aptana provides a special exception to allow redistribution of this file * with certain Eclipse Public Licensed code and certain additional terms * pursuant to Section 7 of the GPL. You may view the exception and these * terms on the web at http://www.aptana.com/legal/gpl/. * * 2. For the Aptana Public License (APL), this program and the * accompanying materials are made available under the terms of the APL * v1.0 which accompanies this distribution, and is available at * http://www.aptana.com/legal/apl/. * * You may view the GPL, Aptana's exception and additional terms, and the * APL in the file titled license.html at the root of the corresponding * plugin containing this source file. * * Any modifications to this file must keep this entire header intact. */ package com.aptana.ide.core.model; import java.net.MalformedURLException; import java.net.URL; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import javax.xml.xpath.XPathConstants; import javax.xml.xpath.XPathExpressionException; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import com.aptana.ide.core.xpath.XPathUtils; /** * @author Kevin Sawicki (ksawicki@aptana.com) * @param <T> */ public abstract class CoreGroupObject<T extends CoreModelObject> extends CoreModelObject { /** * Children in this group */ protected List<T> children; /** * Does this group require child items to have unique ids to be added to the group? */ protected boolean requireUniqueIds; /** * Child template object */ protected T childTemplate; /** * Creates a new grouped object */ public CoreGroupObject() { children = new ArrayList<T>(); childTemplate = createItem(); this.requireUniqueIds = true; } /** * Gets an item with the passed in id * * @param itemId * @return - item or null if no item found with the id passed in */ public T getItem(String itemId) { if (itemId != null) { for (T item : getItems()) { if (itemId.equals(item.getId())) { return item; } } } return null; } /** * Should the new item be added to this group * * @param item * @return - true if the new item should be added */ public abstract boolean shouldAdd(T item); /** * Gets the items in this group * * @return - array of core model objects */ public T[] getItems() { return this.children.toArray(createEmptyArray()); } /** * Clears all items in this group */ public synchronized void clear() { if (this.children != null) { isLoading = true; for (T item : getItems()) { item.clear(); } this.children.clear(); } } /** * @see com.aptana.ide.core.model.BaseModelObject#addListener(com.aptana.ide.core.model.IModelListener) */ public void addListener(IModelListener listener) { super.addListener(listener); for (T item : getItems()) { item.addListener(listener); } } /** * @see com.aptana.ide.core.model.ITransformObject#fromNode(org.w3c.dom.Node) */ public void fromNode(Node node) { try { NodeList set = (NodeList) XPATH.evaluate(getItemString(), node, XPathConstants.NODESET); updateGroup(set); } catch (XPathExpressionException e1) { isLoading = false; String message = MessageFormat.format( Messages.getString("CoreGroupObject.XPath_Error_During_Parse"), //$NON-NLS-1$ new Object[] { getLoggingPrefix(), e1.getMessage() } ); logError(message); } } /** * Creates an empty array * * @return - empty array */ protected abstract T[] createEmptyArray(); /** * Creates a new item * * @return - item */ public abstract T createItem(); /** * Configures the item with some initial state such as logging, service provider, and request builder taken from * this group object * * @param item */ protected void configureItem(T item) { configurationLocation(item); item.setRequestBuilder(getRequestBuilder()); item.setServiceProvider(getServiceProvider()); } /** * Configures the location for the item from the id and location of the group object * * @param item */ protected void configurationLocation(T item) { if (item.getId() != null && getLocation() != null) { String itemLocation = getLocation().toExternalForm(); if (!itemLocation.endsWith("/")) //$NON-NLS-1$ { itemLocation += "/"; //$NON-NLS-1$ } itemLocation += item.getId(); try { URL itemURL = new URL(itemLocation); item.setLocation(itemURL); } catch (MalformedURLException e) { String message = MessageFormat.format( Messages.getString("CoreGroupObject.Error_During_Build"), //$NON-NLS-1$ new Object[] { getLoggingPrefix(), e.getMessage() } ); logError(message); } } } /** * Is the group loading? */ protected boolean isLoading = true; /** * @see com.aptana.ide.core.model.CoreModelObject#update() */ public IServiceErrors update() { IServiceErrors errors = null; isLoading = true; try { errors = super.update(); } finally { isLoading = false; } return errors; } /** * True if the group is loading * * @return - true if loading */ public boolean isLoading() { return isLoading; } private void updateGroup(NodeList set) { try { List<T> newChildren = new ArrayList<T>(); if (requireUniqueIds) { synchronized (this) { newChildren.addAll(this.children); } } List<String> ids = new ArrayList<String>(); for (int i = 0; i < set.getLength(); i++) { T groupItem = null; boolean newItem = true; if (requireUniqueIds) { String id = getTextContent(ID_ELEMENT, set.item(i)); if (id != null && id.length() > 0) { ids.add(id); for (T item : this.children) { if (id.equals(item.getId())) { groupItem = item; newItem = false; break; } } if (groupItem == null) { groupItem = createItem(); if (requireUniqueIds) { groupItem.setId(id); } } } } else { groupItem = createItem(); } if (groupItem != null) { groupItem.suspendEvents(); groupItem.setLogger(getLogger()); groupItem.addListeners(getListeners()); groupItem.fromNode(set.item(i)); configureItem(groupItem); if (newItem && shouldAdd(groupItem)) { newChildren.add(groupItem); groupItem.fireChange(); } groupItem.resumeEvents(); } } if (requireUniqueIds) { Iterator<T> iterator = newChildren.iterator(); while (iterator.hasNext()) { T removedItem = iterator.next(); if (!ids.contains(removedItem.getId())) { iterator.remove(); removedItem.clear(); } } } synchronized (this) { this.children = newChildren; } } catch (Exception e1) { String message = MessageFormat.format( Messages.getString("CoreGroupObject.XPath_Error_During_Parse"), //$NON-NLS-1$ new Object[] { getLoggingPrefix(), e1.getMessage() } ); logError(message); } finally { isLoading = false; } } /** * @see com.aptana.ide.core.model.ITransformObject#fromXML(java.lang.String) */ public void fromXML(String xml) { try { NodeList set = (NodeList) XPATH.evaluate("/" + getGroupString() + "/" + getItemString(), XPathUtils //$NON-NLS-1$ //$NON-NLS-2$ .createSource(xml), XPathConstants.NODESET); updateGroup(set); } catch (XPathExpressionException e1) { isLoading = false; String message = MessageFormat.format( Messages.getString("CoreGroupObject.XPath_Error_During_Parse"), //$NON-NLS-1$ new Object[] { getLoggingPrefix(), e1.getMessage() } ); logError(message); } } /** * @see com.aptana.ide.core.model.ITransformObject#toNode() */ public Node toNode() { return null; } /** * @see com.aptana.ide.core.model.ITransformObject#toXML() */ public String toXML() { StringBuffer buffer = new StringBuffer(); buffer.append("<" + getGroupString() + ">"); //$NON-NLS-1$ //$NON-NLS-2$ for (T item : getItems()) { buffer.append(item.toXML()); } buffer.append("</" + getGroupString() + ">"); //$NON-NLS-1$ //$NON-NLS-2$ return buffer.toString(); } /** * Gets the group string to use for the XPath * * @return - group element string */ protected abstract String getGroupString(); /** * @see com.aptana.ide.core.model.CoreModelObject#getItemString() */ protected String getItemString() { if (childTemplate != null) { return childTemplate.getItemString(); } return null; } /** * @return the requireUniqueIds */ public boolean isRequireUniqueIds() { return requireUniqueIds; } /** * @param requireUniqueIds * the requireUniqueIds to set */ public void setRequireUniqueIds(boolean requireUniqueIds) { this.requireUniqueIds = requireUniqueIds; } /** * Adds the child item * @param items * the child to add */ public void addChild(T item) { this.children.add(item); } }