001/*
002 * Copyright (c) 2016 Chris K Wensel <chris@wensel.net>. All Rights Reserved.
003 * Copyright (c) 2007-2017 Xplenty, Inc. All Rights Reserved.
004 *
005 * Project and contact information: http://www.cascading.org/
006 *
007 * This file is part of the Cascading project.
008 *
009 * Licensed under the Apache License, Version 2.0 (the "License");
010 * you may not use this file except in compliance with the License.
011 * You may obtain a copy of the License at
012 *
013 *     http://www.apache.org/licenses/LICENSE-2.0
014 *
015 * Unless required by applicable law or agreed to in writing, software
016 * distributed under the License is distributed on an "AS IS" BASIS,
017 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
018 * See the License for the specific language governing permissions and
019 * limitations under the License.
020 */
021
022package cascading.flow.planner.iso.expression;
023
024import java.io.File;
025import java.io.FileWriter;
026import java.io.IOException;
027import java.io.Writer;
028
029import cascading.flow.planner.PlannerContext;
030import cascading.flow.planner.Scope;
031import cascading.flow.planner.graph.ElementGraph;
032import cascading.flow.planner.iso.finder.SearchOrder;
033import cascading.util.Util;
034import cascading.util.jgrapht.DOTExporter;
035import cascading.util.jgrapht.IntegerNameProvider;
036import cascading.util.jgrapht.StringEdgeNameProvider;
037import cascading.util.jgrapht.StringNameProvider;
038import org.jgrapht.graph.ClassBasedEdgeFactory;
039import org.jgrapht.graph.DirectedMultigraph;
040import org.slf4j.Logger;
041import org.slf4j.LoggerFactory;
042
043/**
044 *
045 */
046public class ExpressionGraph
047  {
048  private static final Logger LOG = LoggerFactory.getLogger( ExpressionGraph.class );
049
050  private final SearchOrder searchOrder;
051  private final DirectedMultigraph<ElementExpression, ScopeExpression> graph;
052
053  private boolean allowNonRecursiveMatching;
054
055  public ExpressionGraph()
056    {
057    this.searchOrder = SearchOrder.ReverseTopological;
058    this.graph = new DirectedMultigraph( new ClassBasedEdgeFactory( PathScopeExpression.class ) );
059    this.allowNonRecursiveMatching = true;
060    }
061
062  public ExpressionGraph( boolean allowNonRecursiveMatching )
063    {
064    this();
065    this.allowNonRecursiveMatching = allowNonRecursiveMatching;
066    }
067
068  public ExpressionGraph( ElementExpression... matchers )
069    {
070    this();
071    arcs( matchers );
072    }
073
074  public ExpressionGraph( SearchOrder searchOrder, ElementExpression... matchers )
075    {
076    this( searchOrder );
077    arcs( matchers );
078    }
079
080  public ExpressionGraph( SearchOrder searchOrder )
081    {
082    this( searchOrder, true );
083    }
084
085  public ExpressionGraph( SearchOrder searchOrder, boolean allowNonRecursiveMatching )
086    {
087    this.searchOrder = searchOrder;
088    this.graph = new DirectedMultigraph( new ClassBasedEdgeFactory( PathScopeExpression.class ) );
089    this.allowNonRecursiveMatching = allowNonRecursiveMatching;
090    }
091
092  public DirectedMultigraph<ElementExpression, ScopeExpression> getGraph()
093    {
094    return graph;
095    }
096
097  public SearchOrder getSearchOrder()
098    {
099    return searchOrder;
100    }
101
102  public boolean supportsNonRecursiveMatch()
103    {
104    return allowNonRecursiveMatching &&
105      getGraph().vertexSet().size() == 1 &&
106      Util.getFirst( getGraph().vertexSet() ).getCapture() == ElementCapture.Primary;
107    }
108
109  public ExpressionGraph setAllowNonRecursiveMatching( boolean allowNonRecursiveMatching )
110    {
111    this.allowNonRecursiveMatching = allowNonRecursiveMatching;
112
113    return this;
114    }
115
116  public ExpressionGraph arcs( ElementExpression... matchers )
117    {
118    ElementExpression lhs = null;
119
120    for( ElementExpression matcher : matchers )
121      {
122      graph.addVertex( matcher );
123
124      if( lhs != null )
125        graph.addEdge( lhs, matcher );
126
127      lhs = matcher;
128      }
129
130    return this;
131    }
132
133  public ExpressionGraph arc( ElementExpression lhsMatcher, ScopeExpression scopeMatcher, ElementExpression rhsMatcher )
134    {
135    graph.addVertex( lhsMatcher );
136    graph.addVertex( rhsMatcher );
137
138    // can never re-use edges, must be wrapped
139    graph.addEdge( lhsMatcher, rhsMatcher, new DelegateScopeExpression( scopeMatcher ) );
140
141    return this;
142    }
143
144  public void writeDOT( String filename )
145    {
146    try
147      {
148      File parentFile = new File( filename ).getParentFile();
149
150      if( parentFile != null && !parentFile.exists() )
151        parentFile.mkdirs();
152
153      Writer writer = new FileWriter( filename );
154
155      new DOTExporter( new IntegerNameProvider(), new StringNameProvider(), new StringEdgeNameProvider() ).export( writer, getGraph() );
156
157      writer.close();
158
159      Util.writePDF( filename );
160      }
161    catch( IOException exception )
162      {
163      LOG.error( "failed printing expression graph to: {}, with exception: {}", filename, exception );
164      }
165    }
166
167  public static ScopeExpression unwind( ScopeExpression scopeExpression )
168    {
169    if( scopeExpression instanceof DelegateScopeExpression )
170      return ( (DelegateScopeExpression) scopeExpression ).delegate;
171
172    return scopeExpression;
173    }
174
175  private static class DelegateScopeExpression extends ScopeExpression
176    {
177    ScopeExpression delegate;
178
179    protected DelegateScopeExpression( ScopeExpression delegate )
180      {
181      this.delegate = delegate;
182      }
183
184    @Override
185    public boolean isCapture()
186      {
187      return delegate.isCapture();
188      }
189
190    @Override
191    public boolean acceptsAll()
192      {
193      return delegate.acceptsAll();
194      }
195
196    @Override
197    public boolean appliesToAllPaths()
198      {
199      return delegate.appliesToAllPaths();
200      }
201
202    @Override
203    public boolean appliesToAnyPath()
204      {
205      return delegate.appliesToAnyPath();
206      }
207
208    @Override
209    public boolean appliesToEachPath()
210      {
211      return delegate.appliesToEachPath();
212      }
213
214    @Override
215    public boolean applies( PlannerContext plannerContext, ElementGraph elementGraph, Scope scope )
216      {
217      return delegate.applies( plannerContext, elementGraph, scope );
218      }
219
220    @Override
221    public String toString()
222      {
223      return delegate.toString();
224      }
225    }
226  }