001/*
002 * Copyright (c) 2007-2022 The Cascading Authors. All Rights Reserved.
003 *
004 * Project and contact information: https://cascading.wensel.net/
005 *
006 * This file is part of the Cascading project.
007 *
008 * Licensed under the Apache License, Version 2.0 (the "License");
009 * you may not use this file except in compliance with the License.
010 * You may obtain a copy of the License at
011 *
012 *     http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing, software
015 * distributed under the License is distributed on an "AS IS" BASIS,
016 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
017 * See the License for the specific language governing permissions and
018 * limitations under the License.
019 */
020
021package cascading.local.tap.neo4j;
022
023import java.util.LinkedHashMap;
024import java.util.LinkedHashSet;
025import java.util.Map;
026import java.util.Objects;
027import java.util.Set;
028import java.util.function.Function;
029import java.util.stream.Collectors;
030
031import com.fasterxml.jackson.annotation.JsonCreator;
032import com.fasterxml.jackson.annotation.JsonGetter;
033import com.fasterxml.jackson.annotation.JsonIgnore;
034import com.fasterxml.jackson.annotation.JsonInclude;
035import com.fasterxml.jackson.annotation.JsonProperty;
036import com.fasterxml.jackson.annotation.JsonSetter;
037import com.fasterxml.jackson.core.JsonPointer;
038import com.fasterxml.jackson.databind.JsonNode;
039import heretical.pointer.path.Pointer;
040
041import static heretical.pointer.path.json.JSONNestedPointerCompiler.COMPILER;
042
043/**
044 *
045 */
046public class JSONGraphSpec implements GraphSpec
047  {
048  public static class Ref
049    {
050    @JsonInclude(JsonInclude.Include.NON_NULL)
051    String fromPointer;
052    @JsonInclude(JsonInclude.Include.NON_NULL)
053    Object defaultValue;
054    @JsonIgnore
055    Function<JsonNode, Object> function;
056
057    public Ref( Function<JsonNode, Object> function )
058      {
059      this.function = function;
060      }
061
062    public Ref( Object defaultValue )
063      {
064      this( null, defaultValue );
065      }
066
067    @JsonCreator
068    public Ref( @JsonProperty("fromPointer") String fromPointer, @JsonProperty("defaultValue") Object defaultValue )
069      {
070      this.fromPointer = fromPointer;
071      this.defaultValue = defaultValue;
072
073      if( fromPointer != null )
074        {
075        Pointer<JsonNode> pointer = COMPILER.compile( fromPointer );
076        function = n -> pointer.at( n ) == null ? defaultValue : JSONUtil.at( pointer, n );
077        }
078      else
079        {
080        function = n -> defaultValue;
081        }
082      }
083
084    public String getFromPointer()
085      {
086      return fromPointer;
087      }
088
089    public Object getDefaultValue()
090      {
091      return defaultValue;
092      }
093
094    public Function<JsonNode, Object> getFunction()
095      {
096      return function;
097      }
098
099    @Override
100    public boolean equals( Object o )
101      {
102      if( this == o )
103        return true;
104      if( !( o instanceof Ref ) )
105        return false;
106      Ref ref = (Ref) o;
107      return Objects.equals( fromPointer, ref.fromPointer ) &&
108        Objects.equals( defaultValue, ref.defaultValue ) &&
109        Objects.equals( function, ref.function );
110      }
111
112    @Override
113    public int hashCode()
114      {
115      return Objects.hash( fromPointer, defaultValue, function );
116      }
117    }
118
119  public static class EdgeSpec
120    {
121    String edgeType;
122    String targetLabel;
123    Map<String, Ref> targetProperties = new LinkedHashMap<>(); // used for match or create
124
125    public EdgeSpec()
126      {
127      }
128
129    public EdgeSpec( String edgeType )
130      {
131      this.edgeType = edgeType;
132      }
133
134    @JsonCreator
135    public EdgeSpec( @JsonProperty("edgeType") String edgeType, @JsonProperty("targetLabel") String targetLabel )
136      {
137      this( edgeType );
138      this.targetLabel = targetLabel;
139      }
140
141    public boolean hasEdgeType()
142      {
143      return getEdgeType() != null;
144      }
145
146    public String getEdgeType()
147      {
148      return edgeType;
149      }
150
151    public boolean hasTargetLabel()
152      {
153      return getTargetLabel() != null;
154      }
155
156    public String getTargetLabel()
157      {
158      return targetLabel;
159      }
160
161    public boolean hasTargetProperties()
162      {
163      return !targetProperties.isEmpty();
164      }
165
166    @JsonGetter("targetProperties")
167    public Map<String, Ref> getRefTargetProperties()
168      {
169      return targetProperties;
170      }
171
172    public Map<String, Function<JsonNode, Object>> getTargetProperties()
173      {
174      return targetProperties.entrySet().stream().collect( Collectors.toMap( Map.Entry::getKey, e -> e.getValue().function ) );
175      }
176
177    public EdgeSpec addTargetLabel( String targetLabel )
178      {
179      this.targetLabel = targetLabel;
180
181      return this;
182      }
183
184    public EdgeSpec addTargetProperty( String property, Object value )
185      {
186      return addTargetProperty( property, new Ref( value ) );
187      }
188
189    public EdgeSpec addTargetProperty( String property, String fromPointer, Object defaultValue )
190      {
191      return addTargetProperty( property, new Ref( fromPointer, defaultValue ) );
192      }
193
194    public EdgeSpec addTargetProperty( String property, Function<JsonNode, Object> function )
195      {
196      return addTargetProperty( property, new Ref( function ) );
197      }
198
199    @JsonSetter("targetProperties")
200    public EdgeSpec addTargetProperty( String property, Ref ref )
201      {
202      if( targetProperties.put( property, ref ) != null )
203        {
204        throw new IllegalArgumentException( "match property: " + property + ", already exists" );
205        }
206
207      return this;
208      }
209
210    @Override
211    public boolean equals( Object o )
212      {
213      if( this == o )
214        return true;
215      if( !( o instanceof EdgeSpec ) )
216        return false;
217      EdgeSpec edgeSpec = (EdgeSpec) o;
218      return Objects.equals( edgeType, edgeSpec.edgeType ) &&
219        Objects.equals( targetLabel, edgeSpec.targetLabel ) &&
220        Objects.equals( targetProperties, edgeSpec.targetProperties );
221      }
222
223    @Override
224    public int hashCode()
225      {
226      return Objects.hash( edgeType, targetLabel, targetProperties );
227      }
228    }
229
230  String nodeLabel;
231  Map<String, Ref> properties = new LinkedHashMap<>(); // used for match or create
232  Set<EdgeSpec> edges = new LinkedHashSet<>();
233  String valuesPointer = null; // return root by default
234  Function<JsonNode, JsonNode> valueRef = n -> n;
235
236  @JsonCreator
237  public JSONGraphSpec( @JsonProperty("nodeLabel") String nodeLabel )
238    {
239    this.nodeLabel = nodeLabel;
240    }
241
242  public String getNodeLabel()
243    {
244    return nodeLabel;
245    }
246
247  public boolean hasNodeLabel()
248    {
249    return getNodeLabel() != null;
250    }
251
252  @JsonGetter("properties")
253  public Map<String, Ref> getRefProperties()
254    {
255    return properties;
256    }
257
258  @JsonIgnore
259  public Map<String, Function<JsonNode, Object>> getProperties()
260    {
261    return properties.entrySet().stream().collect( Collectors.toMap( Map.Entry::getKey, e -> e.getValue().function ) );
262    }
263
264  public boolean hasProperties()
265    {
266    return !properties.isEmpty();
267    }
268
269  public JSONGraphSpec addProperty( String property, Object value )
270    {
271    return addProperty( property, new Ref( value ) );
272    }
273
274  public JSONGraphSpec addProperty( String property, String fromPointer, Object defaultValue )
275    {
276    return addProperty( property, new Ref( fromPointer, defaultValue ) );
277    }
278
279  public JSONGraphSpec addProperty( String property, Function<JsonNode, Object> function )
280    {
281    return addProperty( property, new Ref( function ) );
282    }
283
284  @JsonSetter("properties")
285  public void addProperties( Map<String, Ref> properties )
286    {
287    this.properties.putAll( properties );
288    }
289
290  public JSONGraphSpec addProperty( String property, Ref ref )
291    {
292    if( properties.put( property, ref ) != null )
293      throw new IllegalArgumentException( "match property: " + property + " already exists" );
294
295    return this;
296    }
297
298  @JsonIgnore
299  public Function<JsonNode, JsonNode> getValuesPointer()
300    {
301    return valueRef;
302    }
303
304  @JsonGetter("valuesPointer")
305  public String getRefValuesPointer()
306    {
307    return valuesPointer;
308    }
309
310  @JsonSetter("valuesPointer")
311  public JSONGraphSpec setValuesPointer( String valuesPointer )
312    {
313    this.valuesPointer = valuesPointer;
314    this.valueRef = n -> n.at( JsonPointer.compile( valuesPointer ) );
315
316    return this;
317    }
318
319  @JsonGetter("edges")
320  public Set<EdgeSpec> getEdges()
321    {
322    return edges;
323    }
324
325  public boolean hasEdges()
326    {
327    return !getEdges().isEmpty();
328    }
329
330  public EdgeSpec addEdge()
331    {
332    return addEdge( (String) null );
333    }
334
335  public EdgeSpec addEdge( String edgeType )
336    {
337    return addEdge( new EdgeSpec( edgeType ) );
338    }
339
340  public EdgeSpec addEdge( EdgeSpec edgeSpec )
341    {
342    edges.add( edgeSpec );
343
344    return edgeSpec;
345    }
346
347  @JsonSetter("edges")
348  public void setEdges( Set<EdgeSpec> edges )
349    {
350    this.edges.addAll( edges );
351    }
352
353  @Override
354  public boolean equals( Object o )
355    {
356    if( this == o )
357      return true;
358    if( !( o instanceof JSONGraphSpec ) )
359      return false;
360    JSONGraphSpec graphSpec = (JSONGraphSpec) o;
361    return Objects.equals( nodeLabel, graphSpec.nodeLabel ) &&
362      Objects.equals( properties, graphSpec.properties ) &&
363      Objects.equals( edges, graphSpec.edges ) &&
364      Objects.equals( valuesPointer, graphSpec.valuesPointer );
365    }
366
367  @Override
368  public int hashCode()
369    {
370    return Objects.hash( nodeLabel, properties, edges, valuesPointer );
371    }
372  }