/*******************************************************************************
* Copyright (c) 2014, 2015 IBM Corporation and others
* All rights reserved. This program and the accompanying materials
* are 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:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.orion.server.cf.manifest.v2;
import java.io.OutputStream;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.eclipse.core.filesystem.EFS;
import org.eclipse.core.filesystem.IFileStore;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.orion.server.core.IOUtilities;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
/**
* Intermediate manifest file representation.
*/
public class ManifestParseTree {
private static final Pattern memoryPattern = Pattern.compile("[1-9][0-9]*(M|MB|G|GB|m|mb|g|gb)"); //$NON-NLS-1$
private static final Pattern nonNegativePattern = Pattern.compile("[1-9][0-9]*"); //$NON-NLS-1$
private List<ManifestParseTree> children;
private ManifestParseTree parent;
private String label = "";
private boolean itemNode;
private int lineNumber;
public ManifestParseTree() {
children = new ArrayList<ManifestParseTree>();
parent = this;
}
public ManifestParseTree(ManifestParseTree node) {
setParent(this);
setItemNode(node.isItemNode());
setLabel(node.getLabel());
/* copy children recursively */
children = new ArrayList<ManifestParseTree>();
for (ManifestParseTree child : node.getChildren()) {
ManifestParseTree newChild = new ManifestParseTree(child);
newChild.setParent(this);
children.add(newChild);
}
}
/**
* Inserts or updates a (key, value) pair to the manifest node.
* @param key
* @param value
*/
public void put(String key, String value) {
if (has(key)) {
try {
ManifestParseTree keyNode = get(key);
keyNode.update(value);
return;
} catch (InvalidAccessException e) {
// it can't happen
// however if happens, try to to add the node instead of updating it
}
}
ManifestParseTree keyNode = new ManifestParseTree();
keyNode.setLabel(key);
ManifestParseTree valueNode = new ManifestParseTree();
keyNode.getChildren().add(valueNode);
valueNode.setParent(keyNode);
valueNode.setLabel(value);
getChildren().add(keyNode);
keyNode.setParent(this);
}
/**
* Inserts or updates a (key, value) pair to the manifest node.
* @param key
* @param object
* @throws JSONException
*/
public void put(String key, JSONObject object) throws JSONException {
if (has(key)) {
try {
ManifestParseTree keyNode = get(key);
for (String k : JSONObject.getNames(object)) {
String v = object.getString(k);
keyNode.put(k, v);
}
return;
} catch (InvalidAccessException e) {
// it can't happen
// however if happens, try to to add the node instead of updating it
}
}
ManifestParseTree keyNode = new ManifestParseTree();
keyNode.setLabel(key);
for (String k : JSONObject.getNames(object)) {
String v = object.getString(k);
keyNode.put(k, v);
}
getChildren().add(keyNode);
keyNode.setParent(this);
}
/**
* Updates the manifest node with the given value.
* @param key
*/
public void update(String value) {
if (getChildren().isEmpty())
return;
ManifestParseTree child = getChildren().get(0);
child.setLabel(value);
}
public void setLabel(String label) {
this.label = label;
}
public String getLabel() {
return label;
}
/**
* Access helper method. Should be used for named children only.
* @param childName Name of the child to be retrieved.
* @return The first child node matching the childName label.
* @throws InvalidAccessException If no matching child could be found.
*/
public ManifestParseTree get(String childName) throws InvalidAccessException {
/* TODO: Consider constant (or amortized constant)
* time access using additional memory. */
for (ManifestParseTree child : children)
if (childName.equals(child.getLabel()))
return child;
throw new InvalidAccessException(this, childName);
}
/**
* Access helper method. Should be used for named children only.
* @param childName Name of the child to be retrieved.
* @return The first child node matching the childName label or <code>null</code> if none present.
*/
public ManifestParseTree getOpt(String childName) {
/* TODO: Consider constant (or amortized constant)
* time access using additional memory. */
for (ManifestParseTree child : children)
if (childName.equals(child.getLabel()))
return child;
return null;
}
/**
* Tests whether the named child exists or not.
* @param childName Name of the child to be tested.
* @return <code>true</code> if and only if the given child exists.
*/
public boolean has(String childName) {
return getOpt(childName) != null;
}
/**
* Access helper method. Should be used for item children only.
* @param kthChild Number of the item child to be retrieved, listed from 0.
* @return The kth item child node.
* @throws InvalidAccessException If no matching child could be found.
*/
public ManifestParseTree get(int kthChild) throws InvalidAccessException {
/* TODO: Consider constant (or amortized constant)
* time access using additional memory. */
int curretChild = -1;
for (ManifestParseTree child : children) {
if (child.isItemNode()) {
++curretChild;
if (curretChild == kthChild)
return child;
}
}
throw new InvalidAccessException(this, kthChild);
}
/**
* Access helper method. Should be used for (key:value) mappings only.
* Removes any starting or ending quotation marks, i.e. " and '.
* @return Label of the first child.
* @throws InvalidAccessException If the nodes has no children.
*/
public String getValue() throws InvalidAccessException {
if (children.isEmpty())
throw new InvalidAccessException(this);
return children.get(0).getLabel().replaceAll("^\"|^\'|\"$|\'$", ""); //$NON-NLS-1$ //$NON-NLS-2$;
}
/**
* @return <code>true</code> if and only if the node represents a list.
*/
public boolean isList() {
if (children.isEmpty())
return false;
return children.get(0).isItemNode();
}
/**
* @return <code>true</code> if and only if the node represents a string property.
*/
public boolean isStringProperty() {
if (getChildren().size() != 1)
return false;
if (isList())
return false;
ManifestParseTree valueNode = getChildren().get(0);
if (valueNode.getChildren().size() != 0)
return false;
return true;
}
/**
* @return <code>true</code> if and only if the node represents a valid application memory property.
*/
public boolean isValidMemoryProperty() {
if (!isStringProperty())
return false;
try {
String memoryValue = getValue();
Matcher matcher = memoryPattern.matcher(memoryValue);
return matcher.matches();
} catch (InvalidAccessException ex) {
return false;
}
}
/**
* @return <code>true</code> if and only if the node represents a valid non-negative valued property.
*/
public boolean isValidNonNegativeProperty() {
if (!isStringProperty())
return false;
try {
String instancesValue = getValue();
Matcher matcher = nonNegativePattern.matcher(instancesValue);
return matcher.matches();
} catch (InvalidAccessException ex) {
return false;
}
}
/**
* Externalization helper method
*/
protected String toString(int indentation) {
StringBuilder sb = new StringBuilder();
if (getParent() == this) {
/* special case: manifest root */
sb.append("---").append(System.getProperty("line.separator")); //$NON-NLS-1$ //$NON-NLS-2$
for (ManifestParseTree child : children)
sb.append(child.toString(0)).append(System.getProperty("line.separator")); //$NON-NLS-1$
return sb.toString();
}
/* print indentation */
for (int i = 0; i < indentation; ++i)
sb.append(" "); //$NON-NLS-1$
String label = getLabel();
// if a label contains colon, the label need to be quoted
if (label != null && label.contains(":")) {
label = "\"" + label + "\"";
}
sb.append(label);
/* print mapping symbol if required */
boolean isItemNode = isItemNode();
if (!isItemNode && children.size() > 0)
sb.append(":"); //$NON-NLS-1$
/* print children nodes */
int childrenSize = children.size();
for (int i = 0; i < childrenSize; ++i) {
ManifestParseTree child = children.get(i);
if ((isItemNode && i == 0) || (childrenSize == 1 && child.getChildren().size() == 0)) {
/* special case: in-line item */
sb.append(" "); //$NON-NLS-1$
sb.append(child.toString(0));
} else {
sb.append(System.getProperty("line.separator")); //$NON-NLS-1$
if (!child.isItemNode() || getParent().isItemNode())
sb.append(child.toString(indentation + 2));
else
sb.append(child.toString(indentation));
}
}
return sb.toString();
}
/**
* Externalization to JSON format.
* @return JSON representation.
* @throws JSONException
* @throws InvalidAccessException
*/
public JSONObject toJSON() throws JSONException, InvalidAccessException {
JSONObject rep = new JSONObject();
for (ManifestParseTree child : getChildren())
child.append(rep);
return rep;
}
/**
* JSON externalization helper.
*/
protected void append(JSONObject rep) throws JSONException, InvalidAccessException {
if (isList()) {
JSONArray arr = new JSONArray();
for (ManifestParseTree child : getChildren())
child.append(arr);
rep.put(getLabel(), arr);
return;
}
if (getChildren().size() == 1 && getChildren().get(0).getChildren().size() == 0) {
/* format: A: B (mapping) */
rep.put(getLabel(), getValue());
return;
}
JSONObject obj = new JSONObject();
for (ManifestParseTree child : getChildren())
child.append(obj);
rep.put(getLabel(), obj);
}
/**
* JSON externalization helper.
*/
protected void append(JSONArray rep) throws JSONException, InvalidAccessException {
if (getChildren().size() == 1 && getChildren().get(0).getChildren().size() == 0) {
/* format: - A (note: no mapping) */
rep.put(getChildren().get(0).getLabel());
return;
}
/* otherwise, we expect an object */
JSONObject obj = new JSONObject();
for (ManifestParseTree child : getChildren())
child.append(obj);
rep.put(obj);
}
/**
* Persists the manifest YAML representation into the given file store.
* @param fileStore File store to persist the manifest. Note: if the given
* file exists, it's contents are going to be overridden.
* @throws CoreException
*/
public void persist(IFileStore fileStore) throws CoreException {
PrintStream ps = null;
try {
String representation = toString();
OutputStream out = fileStore.openOutputStream(EFS.OVERWRITE, null);
ps = new PrintStream(out);
ps.print(representation);
} finally {
if (ps != null)
IOUtilities.safeClose(ps);
}
}
public boolean isRoot() {
return parent == this;
}
public void setItemNode(boolean itemNode) {
this.itemNode = itemNode;
}
public boolean isItemNode() {
return itemNode;
}
public List<ManifestParseTree> getChildren() {
return children;
}
public ManifestParseTree getParent() {
return parent;
}
public void setParent(ManifestParseTree father) {
this.parent = father;
}
public void setLineNumber(int lineNumber) {
this.lineNumber = lineNumber;
}
public int getLineNumber() {
return lineNumber;
}
@Override
public String toString() {
return toString(0);
}
@Override
public boolean equals(Object obj) {
if (obj == null || !(obj instanceof ManifestParseTree))
return false;
ManifestParseTree tree = (ManifestParseTree) obj;
return getLabel().equals(tree.getLabel());
}
@Override
public int hashCode() {
return getLabel().hashCode();
}
}