/* * Copyright (c) 2002-2017 "Neo Technology," * Network Engine for Objects in Lund AB [http://neotechnology.com] * * This file is part of Neo4j. * * Licensed 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.neo4j.driver.v1.tck.tck.util; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import org.neo4j.driver.internal.InternalPath; import org.neo4j.driver.internal.value.IntegerValue; import org.neo4j.driver.internal.value.ListValue; import org.neo4j.driver.internal.value.NodeValue; import org.neo4j.driver.internal.value.NullValue; import org.neo4j.driver.internal.value.PathValue; import org.neo4j.driver.internal.value.RelationshipValue; import org.neo4j.driver.internal.value.StringValue; import org.neo4j.driver.v1.types.Entity; import org.neo4j.driver.v1.types.Path; import org.neo4j.driver.v1.Value; import org.neo4j.driver.v1.Values; import static java.lang.String.format; import static org.junit.Assert.assertEquals; import static org.neo4j.driver.internal.types.InternalTypeSystem.TYPE_SYSTEM; import static org.neo4j.driver.v1.Values.ofValue; import static org.neo4j.driver.v1.tck.tck.util.Types.Type; import static org.neo4j.driver.v1.tck.tck.util.Types.getTypeFromStringConstellation; public class ResultParser { private static Value parseString( String resultValue ) { return new StringValue( (String) Type.String.getJavaValue( resultValue ) ); } private static NodeValue parseNode( String input ) { if ( input.charAt( 0 ) != '(' && input.charAt( input.length() - 1 ) != ')' ) { throw new IllegalArgumentException( format( "Node representation should start with ( and end with " + "). Got: {%s}", input ) ); } Map<String,Value> properties = getProperties( input ); Collection<String> labels = getLabels( input ); return new TestNodeValue( 0, labels, properties ); } private static RelationshipValue parseRelationship( String input ) { if ( input.charAt( 0 ) != '[' && input.charAt( input.length() - 1 ) != ']' ) { throw new IllegalArgumentException( format( "Relationship representation should start with [ and end with ]. Got: {%s}", input ) ); } Map<String,Value> properties = getProperties( input ); Collection<String> type = getLabels( input ); if ( type.size() > 1 ) { throw new IllegalArgumentException( "Labels should only have single type. Found: " + type ); } return new TestRelationshipValue( 0, type.iterator().next(), properties ); } private static PathValue parsePath( String input ) { input = input.substring( 1, input.length() - 1 ); ArrayList<Entity> nodesAndRels = new ArrayList<>(); int id = 0; if ( input.charAt( 0 ) != '(' ) { throw new IllegalArgumentException( format( "Path should start with node (. Got: [%s]", input ) ); } String nstart = input.substring( 0, input.indexOf( ')' ) + 1 ); input = input.substring( input.indexOf( ')' ) + 1 ); TestNodeValue startNode = (TestNodeValue) parseNode( nstart ); nodesAndRels.add( startNode ); startNode.setId( id++ ); TestNodeValue prev_n = startNode; while ( input.length() > 0 ) { String n, rel; int split_at; boolean posDirection; if ( input.startsWith( "<-" ) ) { input = input.substring( 2 ); if ( !input.contains( "]-" ) ) { throw new IllegalArgumentException( format( "Relationship in path representation should end with -]. Got: {%s}", input ) ); } split_at = input.indexOf( "]-" ); rel = input.substring( 0, split_at + 1 ); input = input.substring( split_at + 2 ); posDirection = false; } else if ( input.startsWith( "-[" ) ) { input = input.substring( 1 ); if ( !input.contains( "]->" ) ) { throw new IllegalArgumentException( format( "Relationship in path representation should end with ]->. Got: {%s}", input ) ); } split_at = input.indexOf( "->" ); rel = input.substring( 0, split_at ); input = input.substring( split_at + 2 ); posDirection = true; } else { throw new IllegalArgumentException( format( "Relationship in path should start with -[ or <-[. Got: %s", input ) ); } if ( input.charAt( 0 ) != '(' ) { throw new IllegalArgumentException( format( "Path should contain node after relationship (. Got: %s", input ) ); } split_at = input.indexOf( ')' ); n = input.substring( 0, split_at + 1 ); input = input.substring( split_at + 1 ); TestNodeValue nValue = (TestNodeValue) parseNode( n ); nValue.setId( id++ ); if ( posDirection ) { TestRelationship relValue = new TestRelationship( (TestRelationshipValue) parseRelationship( rel ), prev_n, nValue ); nodesAndRels.add( relValue ); } else { TestRelationship relValue = new TestRelationship( (TestRelationshipValue) parseRelationship( rel ), nValue, prev_n ); nodesAndRels.add( relValue ); } prev_n = nValue; nodesAndRels.add( nValue ); } return new PathValue( new InternalPath( nodesAndRels ) ); } private static Collection<String> getLabels( String input ) { input = input.substring( 1, input.length() - 1 ); int i1 = input.indexOf( "{" ); if ( i1 != -1 ) { int i2 = input.lastIndexOf( "}" ); input = input.substring( 0, i1 ) + input.substring( i2 + 1 ); } String[] values = input.split( ":" ); if ( values[values.length - 1].contains( " " ) ) { String s = values[values.length - 1]; values[values.length - 1] = s.substring( 0, s.indexOf( " " ) ); } values = Arrays.copyOfRange( values, 1, values.length ); return Arrays.asList( values ); } private static Map<String,Value> getProperties( String input ) { Map<String,Object> result = getMapOfObjects( input, false ); HashMap<String,Value> properties = new HashMap<>(); for ( String key : result.keySet() ) { properties.put( key, Values.value( result.get( key ) ) ); } return properties; } private static PathValue pathToTestPath( Path p ) { ArrayList<Entity> nodesAndRels = new ArrayList<>(); TestNodeValue start = new TestNodeValue( p.start() ); TestNodeValue prevNode = start; nodesAndRels.add( start ); for ( Path.Segment segment : p ) { TestNodeValue node = new TestNodeValue( segment.end() ); TestRelationshipValue trv = new TestRelationshipValue( segment.relationship() ); if ( trv.asRelationship().startNodeId() == segment.start().id() && trv.asRelationship().endNodeId() == segment.end().id() ) { nodesAndRels.add( new TestRelationship( trv, prevNode, node ) ); } else if ( trv.asRelationship().startNodeId() == segment.end().id() && trv.asRelationship().endNodeId() == segment.start().id() ) { nodesAndRels.add( new TestRelationship( trv, node, prevNode ) ); } else { throw new IllegalArgumentException( "Relationship start and stop does not match the segment " + "nodes" ); } nodesAndRels.add( node ); prevNode = node; } return new PathValue( new InternalPath( nodesAndRels ) ); } public static Map<String,Value> parseGiven( Map<String,Value> input ) { Map<String,Value> converted = new HashMap<>(); for ( String key : input.keySet() ) { converted.put( key, parseGiven( input.get( key ) ) ); } return converted; } public static Value parseGiven( Value input ) { if ( TYPE_SYSTEM.LIST().isTypeOf( input ) ) { List<Value> vals = new ArrayList<>(); List<Value> givenVals = input.asList( ofValue() ); for ( Value givenValue : givenVals ) { vals.add( parseGiven( givenValue ) ); } return new ListValue( vals.toArray( new Value[vals.size()] ) ); } if ( TYPE_SYSTEM.INTEGER().isTypeOf( input ) || TYPE_SYSTEM.FLOAT().isTypeOf( input ) || TYPE_SYSTEM.BOOLEAN().isTypeOf( input ) || TYPE_SYSTEM.STRING().isTypeOf( input ) || TYPE_SYSTEM.NULL().isTypeOf( input ) ) { return input; } else if ( TYPE_SYSTEM.NODE().isTypeOf( input ) ) { return new TestNodeValue( input.asNode() ); } else if ( TYPE_SYSTEM.RELATIONSHIP().isTypeOf( input ) ) { return new TestRelationshipValue( input.asRelationship() ); } else if ( TYPE_SYSTEM.PATH().isTypeOf( input ) ) { return pathToTestPath( input.asPath() ); } else { throw new IllegalArgumentException( format( "Type not handled for: %s", input ) ); } } public static Object getJavaValueIntAsLong( String value ) { return getJavaValue( value, false ); } public static Object getJavaValueNormalInts( String value ) { return getJavaValue( value, true ); } private static Object getJavaValue( String value, boolean normalInts ) { if ( isList( value ) ) { ArrayList<Object> values = new ArrayList<>(); for ( String val : getList( value ) ) { values.add( Types.asObject( val ) ); } return values; } else if ( isMap( value ) ) { return getMapOfObjects( value, normalInts ); } else { return Types.asObject( value ); } } public static Value parseStringValue( String value ) { if ( isList( value ) ) { String[] resultValues = getList( value ); Value[] valueArray = new Value[resultValues.length]; for ( int j = 0; j < resultValues.length; j++ ) { Type type = getTypeFromStringConstellation( resultValues[j] ); valueArray[j] = createValue( resultValues[j], type ); } return new ListValue( valueArray ); } else { return createValue( value, getTypeFromStringConstellation( value ) ); } } public static Map<String,Value> parseExpected( Collection<String> input, List<String> keys ) { assertEquals( keys.size(), input.size() ); Map<String,Value> converted = new HashMap<>(); int i = 0; for ( String resultValue : input ) { String key = keys.get( i ); converted.put( key, parseStringValue( resultValue ) ); i++; } return converted; } public static String[] getList( String resultValue ) { return resultValue.substring( 1, resultValue.length() - 1 ).split( ", " ); } public static Map<String,Object> getMapOfObjects( String input, boolean normalInts ) { Map<String,Object> properties = new HashMap<>(); int i1 = input.indexOf( "{" ); if ( i1 == -1 ) { return properties; } int i2 = input.lastIndexOf( "}" ); input = input.substring( i1, i2 + 1 ); try { ObjectMapper mapper = new ObjectMapper(); mapper.configure( DeserializationFeature.USE_LONG_FOR_INTS, !normalInts ); properties = mapper.readValue( input, HashMap.class ); } catch ( IOException e ) { throw new IllegalArgumentException( "Not able to parse Node: " + input, e ); } return properties; } public static boolean isList( String resultValue ) { return resultValue.startsWith( "[" ) && resultValue.endsWith( "]" ) && resultValue.charAt( 1 ) != ':'; } public static boolean isMap( String resultValue ) { return resultValue.startsWith( "{" ) && resultValue.endsWith( "}" ); } private static Value createValue( String resultValue, Type type ) { switch ( type ) { case Null: return NullValue.NULL; case Integer: return new IntegerValue( Long.valueOf( resultValue ) ); case String: return parseString( resultValue ); case Node: return parseNode( resultValue ); case Relationship: return parseRelationship( resultValue ); case Path: return parsePath( resultValue ); default: throw new IllegalArgumentException( format( "Type not recognized: %s", type ) ); } } }