/** * Copyright (c) 2011 Cloudsmith Inc. and other contributors, as listed below. * 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: * Cloudsmith * */ package org.cloudsmith.geppetto.forge.util; import java.io.File; import java.io.IOException; import java.io.Reader; import java.io.StringReader; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import org.cloudsmith.geppetto.forge.FilePosition; import org.codehaus.jackson.JsonFactory; import org.codehaus.jackson.JsonLocation; import org.codehaus.jackson.JsonParseException; import org.codehaus.jackson.JsonParser; import org.codehaus.jackson.JsonToken; public class JsonPositionalParser { public static class JArray extends JElement { private final List<JElement> elements; JArray(Object sourceRef, int[] pos, List<JElement> elements) { super(sourceRef, pos); this.elements = elements; } @Override public List<Object> getValue() { int top = elements.size(); ArrayList<Object> values = new ArrayList<Object>(top); for(int idx = 0; idx < top; ++idx) values.add(elements.get(idx).getValue()); return values; } public List<JElement> getValues() { return elements; } } public static abstract class JElement implements FilePosition { private final int offset; private final int length; private final int line; private final File file; JElement(Object sourceRef, int[] pos) { this.file = (File) sourceRef; this.line = pos[0]; this.offset = pos[1]; this.length = pos[2]; } public File getFile() { return file; } public int getLength() { return length; } public int getLine() { return line; } public int getOffset() { return offset; } public abstract Object getValue(); public String toStringOrNull() { return null; } } public static class JEntry extends JElement { private final String key; private final JElement value; JEntry(Object sourceRef, int[] pos, String key, JElement value) { super(sourceRef, pos); this.key = key; this.value = value; } public JElement getElement() { return value; } public String getKey() { return key; } @Override public Object getValue() { return value == null ? null : value.getValue(); } } public static class JObject extends JElement { private final List<JEntry> entries; JObject(Object sourceRef, int[] pos, List<JEntry> entries) { super(sourceRef, pos); this.entries = entries; } public List<JEntry> getEntries() { return entries; } @Override public Map<String, Object> getValue() { int idx = entries.size(); Map<String, Object> map = new HashMap<String, Object>(idx); while(--idx >= 0) { JEntry entry = entries.get(idx); map.put(entry.getKey(), entry.getValue()); } return map; } } public static class JPrimitive extends JElement { private final Object value; JPrimitive(Object sourceRef, int[] pos, Object value) { super(sourceRef, pos); this.value = value; } @Override public Object getValue() { return value; } @Override public String toStringOrNull() { return value instanceof String ? (String) value : null; } } static class JsonExtFactory extends JsonFactory { public JsonParser createJsonParser(File f, Reader reader) throws IOException, JsonParseException { return _createJsonParser(reader, _createContext(f, true)); } } private JsonParser jp; private String content; private int[] adjustPosition(int line, int offset, int length) { int top = content.length(); ++length; while(offset < top) { char c = content.charAt(offset); if(!(c == ',' || Character.isWhitespace(c))) break; ++offset; --length; if(c == '\n') ++line; } return new int[] { line, offset, length }; } public JElement parse(File file, String content) throws JsonParseException, IOException { JsonExtFactory jsonFactory = new JsonExtFactory(); this.content = content; jp = jsonFactory.createJsonParser(file, new StringReader(content)); JsonToken token = jp.nextToken(); if(token == JsonToken.NOT_AVAILABLE) return null; return parseValue(token); } private JArray parseArray() throws JsonParseException, IOException { JsonLocation loc = jp.getTokenLocation(); long startPos = loc.getCharOffset(); int line = loc.getLineNr(); List<JElement> values = new ArrayList<JElement>(); for(;;) { JsonToken token = jp.nextToken(); if(token == JsonToken.END_ARRAY) { int[] pos = adjustPosition( line, (int) startPos, (int) (jp.getCurrentLocation().getCharOffset() - startPos)); return new JArray(loc.getSourceRef(), pos, values); } values.add(parseValue(token)); } } private JEntry parseField() throws JsonParseException, IOException { JsonLocation loc = jp.getTokenLocation(); long startPos = loc.getCharOffset(); int line = loc.getLineNr(); String key = jp.getCurrentName(); JElement value = parseValue(jp.nextToken()); // We get weird positions from the parser that includes whitespace etc. We adjust // that here int[] pos = adjustPosition(line, (int) startPos, (int) (jp.getCurrentLocation().getCharOffset() - startPos)); return new JEntry(loc.getSourceRef(), pos, key, value); } private JObject parseObject() throws JsonParseException, IOException { JsonLocation loc = jp.getTokenLocation(); long startPos = loc.getCharOffset(); int line = loc.getLineNr(); List<JEntry> entries = new ArrayList<JEntry>(); for(;;) { switch(jp.nextToken()) { case END_OBJECT: int[] pos = adjustPosition( line, (int) startPos, (int) (jp.getCurrentLocation().getCharOffset() - startPos)); return new JObject(loc.getSourceRef(), pos, entries); case FIELD_NAME: entries.add(parseField()); continue; default: throw new JsonParseException("Field name expected", jp.getTokenLocation()); } } } private JElement parseValue(JsonToken token) throws JsonParseException, IOException { JsonLocation loc = jp.getCurrentLocation(); long startPos = loc.getCharOffset(); int line = loc.getLineNr(); switch(token) { case START_ARRAY: return parseArray(); case START_OBJECT: return parseObject(); case VALUE_FALSE: case VALUE_NULL: case VALUE_TRUE: case VALUE_NUMBER_FLOAT: case VALUE_NUMBER_INT: case VALUE_STRING: Object value = null; switch(token) { case VALUE_FALSE: value = Boolean.FALSE; break; case VALUE_TRUE: value = Boolean.TRUE; break; case VALUE_NUMBER_FLOAT: value = jp.getDoubleValue(); break; case VALUE_NUMBER_INT: value = jp.getLongValue(); break; case VALUE_STRING: value = jp.getText(); } long endPos = jp.getCurrentLocation().getCharOffset(); int[] pos = adjustPosition(line, (int) startPos, (int) (endPos - startPos)); return new JPrimitive(loc.getSourceRef(), pos, value); default: throw new JsonParseException("Value expected", loc); } } }