001    /*
002     * Copyright (c) 2007-2014 Concurrent, Inc. All Rights Reserved.
003     *
004     * Project and contact information: http://www.cascading.org/
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    
021    package cascading.util;
022    
023    import java.beans.Expression;
024    import java.io.FileWriter;
025    import java.io.IOException;
026    import java.io.PrintStream;
027    import java.io.PrintWriter;
028    import java.io.StringWriter;
029    import java.io.Writer;
030    import java.lang.reflect.Constructor;
031    import java.lang.reflect.Field;
032    import java.lang.reflect.Method;
033    import java.lang.reflect.Type;
034    import java.security.MessageDigest;
035    import java.security.NoSuchAlgorithmException;
036    import java.util.ArrayList;
037    import java.util.Arrays;
038    import java.util.Collection;
039    import java.util.Collections;
040    import java.util.HashSet;
041    import java.util.Iterator;
042    import java.util.LinkedHashSet;
043    import java.util.List;
044    import java.util.Set;
045    import java.util.UUID;
046    
047    import cascading.CascadingException;
048    import cascading.flow.FlowElement;
049    import cascading.flow.FlowException;
050    import cascading.flow.planner.Scope;
051    import cascading.operation.Operation;
052    import cascading.pipe.Pipe;
053    import cascading.scheme.Scheme;
054    import cascading.tap.MultiSourceTap;
055    import cascading.tap.Tap;
056    import org.jgrapht.ext.DOTExporter;
057    import org.jgrapht.ext.EdgeNameProvider;
058    import org.jgrapht.ext.IntegerNameProvider;
059    import org.jgrapht.ext.MatrixExporter;
060    import org.jgrapht.ext.VertexNameProvider;
061    import org.jgrapht.graph.SimpleDirectedGraph;
062    import org.slf4j.Logger;
063    import org.slf4j.LoggerFactory;
064    
065    /** Class Util provides reusable operations. */
066    public class Util
067      {
068      public static int ID_LENGTH = 32;
069    
070      private static final Logger LOG = LoggerFactory.getLogger( Util.class );
071      private static final String HEXES = "0123456789ABCDEF";
072    
073      public static synchronized String createUniqueID()
074        {
075        // creates a cryptographically secure random value
076        String value = UUID.randomUUID().toString();
077        return value.toUpperCase().replaceAll( "-", "" );
078        }
079    
080      public static String createID( String rawID )
081        {
082        return createID( rawID.getBytes() );
083        }
084    
085      /**
086       * Method CreateID returns a HEX hash of the given bytes with length 32 characters long.
087       *
088       * @param bytes the bytes
089       * @return string
090       */
091      public static String createID( byte[] bytes )
092        {
093        try
094          {
095          return getHex( MessageDigest.getInstance( "MD5" ).digest( bytes ) );
096          }
097        catch( NoSuchAlgorithmException exception )
098          {
099          throw new RuntimeException( "unable to digest string" );
100          }
101        }
102    
103      private static String getHex( byte[] bytes )
104        {
105        if( bytes == null )
106          return null;
107    
108        final StringBuilder hex = new StringBuilder( 2 * bytes.length );
109    
110        for( final byte b : bytes )
111          hex.append( HEXES.charAt( ( b & 0xF0 ) >> 4 ) ).append( HEXES.charAt( b & 0x0F ) );
112    
113        return hex.toString();
114        }
115    
116      public static <T> T[] copy( T[] source )
117        {
118        if( source == null )
119          return null;
120    
121        return Arrays.copyOf( source, source.length );
122        }
123    
124      public static String unique( String value, String delim )
125        {
126        String[] split = value.split( delim );
127    
128        Set<String> values = new LinkedHashSet<String>();
129    
130        Collections.addAll( values, split );
131    
132        return join( values, delim );
133        }
134    
135      /**
136       * This method joins the values in the given list with the delim String value.
137       *
138       * @param list
139       * @param delim
140       * @return String
141       */
142      public static String join( int[] list, String delim )
143        {
144        return join( list, delim, false );
145        }
146    
147      public static String join( int[] list, String delim, boolean printNull )
148        {
149        StringBuffer buffer = new StringBuffer();
150        int count = 0;
151    
152        for( Object s : list )
153          {
154          if( count != 0 )
155            buffer.append( delim );
156    
157          if( printNull || s != null )
158            buffer.append( s );
159    
160          count++;
161          }
162    
163        return buffer.toString();
164        }
165    
166      public static String join( String delim, String... strings )
167        {
168        return join( delim, false, strings );
169        }
170    
171      public static String join( String delim, boolean printNull, String... strings )
172        {
173        return join( strings, delim, printNull );
174        }
175    
176      /**
177       * This method joins the values in the given list with the delim String value.
178       *
179       * @param list
180       * @param delim
181       * @return a String
182       */
183      public static String join( Object[] list, String delim )
184        {
185        return join( list, delim, false );
186        }
187    
188      public static String join( Object[] list, String delim, boolean printNull )
189        {
190        return join( list, delim, printNull, 0 );
191        }
192    
193      public static String join( Object[] list, String delim, boolean printNull, int beginAt )
194        {
195        return join( list, delim, printNull, beginAt, list.length - beginAt );
196        }
197    
198      public static String join( Object[] list, String delim, boolean printNull, int beginAt, int length )
199        {
200        StringBuffer buffer = new StringBuffer();
201        int count = 0;
202    
203        for( int i = beginAt; i < beginAt + length; i++ )
204          {
205          Object s = list[ i ];
206          if( count != 0 )
207            buffer.append( delim );
208    
209          if( printNull || s != null )
210            buffer.append( s );
211    
212          count++;
213          }
214    
215        return buffer.toString();
216        }
217    
218      public static String join( Iterable iterable, String delim, boolean printNull )
219        {
220        int count = 0;
221    
222        StringBuilder buffer = new StringBuilder();
223    
224        for( Object s : iterable )
225          {
226          if( count != 0 )
227            buffer.append( delim );
228    
229          if( printNull || s != null )
230            buffer.append( s );
231    
232          count++;
233          }
234    
235        return buffer.toString();
236        }
237    
238      /**
239       * This method joins each value in the collection with a tab character as the delimiter.
240       *
241       * @param collection
242       * @return a String
243       */
244      public static String join( Collection collection )
245        {
246        return join( collection, "\t" );
247        }
248    
249      /**
250       * This method joins each valuein the collection with the given delimiter.
251       *
252       * @param collection
253       * @param delim
254       * @return a String
255       */
256      public static String join( Collection collection, String delim )
257        {
258        return join( collection, delim, false );
259        }
260    
261      public static String join( Collection collection, String delim, boolean printNull )
262        {
263        StringBuffer buffer = new StringBuffer();
264    
265        join( buffer, collection, delim, printNull );
266    
267        return buffer.toString();
268        }
269    
270      /**
271       * This method joins each value in the collection with the given delimiter. All results are appended to the
272       * given {@link StringBuffer} instance.
273       *
274       * @param buffer
275       * @param collection
276       * @param delim
277       */
278      public static void join( StringBuffer buffer, Collection collection, String delim )
279        {
280        join( buffer, collection, delim, false );
281        }
282    
283      public static void join( StringBuffer buffer, Collection collection, String delim, boolean printNull )
284        {
285        int count = 0;
286    
287        for( Object s : collection )
288          {
289          if( count != 0 )
290            buffer.append( delim );
291    
292          if( printNull || s != null )
293            buffer.append( s );
294    
295          count++;
296          }
297        }
298    
299      public static String[] removeNulls( String... strings )
300        {
301        List<String> list = new ArrayList<String>();
302    
303        for( String string : strings )
304          {
305          if( string != null )
306            list.add( string );
307          }
308    
309        return list.toArray( new String[ list.size() ] );
310        }
311    
312      public static Collection<String> quote( Collection<String> collection, String quote )
313        {
314        List<String> list = new ArrayList<String>();
315    
316        for( String string : collection )
317          list.add( quote + string + quote );
318    
319        return list;
320        }
321    
322      public static String print( Collection collection, String delim )
323        {
324        StringBuffer buffer = new StringBuffer();
325    
326        print( buffer, collection, delim );
327    
328        return buffer.toString();
329        }
330    
331      public static void print( StringBuffer buffer, Collection collection, String delim )
332        {
333        int count = 0;
334    
335        for( Object s : collection )
336          {
337          if( count != 0 )
338            buffer.append( delim );
339    
340          buffer.append( "[" );
341          buffer.append( s );
342          buffer.append( "]" );
343    
344          count++;
345          }
346        }
347    
348      /**
349       * This method attempts to remove any username and password from the given url String.
350       *
351       * @param url
352       * @return a String
353       */
354      public static String sanitizeUrl( String url )
355        {
356        if( url == null )
357          return null;
358    
359        return url.replaceAll( "(?<=//).*:.*@", "" );
360        }
361    
362      /**
363       * This method attempts to remove duplicate consecutive forward slashes from the given url.
364       *
365       * @param url
366       * @return a String
367       */
368      public static String normalizeUrl( String url )
369        {
370        if( url == null )
371          return null;
372    
373        return url.replaceAll( "([^:]/)/{2,}", "$1/" );
374        }
375    
376      /**
377       * This method returns the {@link Object#toString()} of the given object, or an empty String if the object
378       * is null.
379       *
380       * @param object
381       * @return a String
382       */
383      public static String toNull( Object object )
384        {
385        if( object == null )
386          return "";
387    
388        return object.toString();
389        }
390    
391      /**
392       * This method truncates the given String value to the given size, but appends an ellipse ("...") if the
393       * String is larger than maxSize.
394       *
395       * @param string
396       * @param maxSize
397       * @return a String
398       */
399      public static String truncate( String string, int maxSize )
400        {
401        string = toNull( string );
402    
403        if( string.length() <= maxSize )
404          return string;
405    
406        return String.format( "%s...", string.subSequence( 0, maxSize - 3 ) );
407        }
408    
409      public static String printGraph( SimpleDirectedGraph graph )
410        {
411        StringWriter writer = new StringWriter();
412    
413        printGraph( writer, graph );
414    
415        return writer.toString();
416        }
417    
418      public static void printGraph( PrintStream out, SimpleDirectedGraph graph )
419        {
420        PrintWriter printWriter = new PrintWriter( out );
421    
422        printGraph( printWriter, graph );
423        }
424    
425      public static void printGraph( String filename, SimpleDirectedGraph graph )
426        {
427        try
428          {
429          Writer writer = new FileWriter( filename );
430    
431          try
432            {
433            printGraph( writer, graph );
434            }
435          finally
436            {
437            writer.close();
438            }
439          }
440        catch( IOException exception )
441          {
442          LOG.error( "failed printing graph to {}, with exception: {}", filename, exception );
443          }
444        }
445    
446      @SuppressWarnings({"unchecked"})
447      private static void printGraph( Writer writer, SimpleDirectedGraph graph )
448        {
449        DOTExporter dot = new DOTExporter( new IntegerNameProvider(), new VertexNameProvider()
450        {
451        public String getVertexName( Object object )
452          {
453          if( object == null )
454            return "none";
455    
456          return object.toString().replaceAll( "\"", "\'" );
457          }
458        }, new EdgeNameProvider<Object>()
459        {
460        public String getEdgeName( Object object )
461          {
462          if( object == null )
463            return "none";
464    
465          return object.toString().replaceAll( "\"", "\'" );
466          }
467        }
468        );
469    
470        dot.export( writer, graph );
471        }
472    
473      public static void printMatrix( PrintStream out, SimpleDirectedGraph<FlowElement, Scope> graph )
474        {
475        new MatrixExporter().exportAdjacencyMatrix( new PrintWriter( out ), graph );
476        }
477    
478      /**
479       * This method removes all nulls from the given List.
480       *
481       * @param list
482       */
483      @SuppressWarnings({"StatementWithEmptyBody"})
484      public static void removeAllNulls( List list )
485        {
486        while( list.remove( null ) )
487          ;
488        }
489    
490      /**
491       * Allows for custom trace fields on Pipe, Tap, and Scheme types
492       *
493       * @deprecated see {@link cascading.util.TraceUtil#setTrace(Object, String)}
494       */
495      @Deprecated
496      public static void setTrace( Object object, String trace )
497        {
498        TraceUtil.setTrace( object, trace );
499        }
500    
501      /**
502       * @deprecated see {@link cascading.util.TraceUtil#captureDebugTrace(Object)}
503       */
504      @Deprecated
505      public static String captureDebugTrace( Class type )
506        {
507        return TraceUtil.captureDebugTrace( type );
508        }
509    
510      public static String formatTrace( final Pipe pipe, String message )
511        {
512        return TraceUtil.formatTrace( pipe, message );
513        }
514    
515      /**
516       * @deprecated see {@link cascading.util.TraceUtil#formatTrace(cascading.tap.Tap, String)}
517       */
518      @Deprecated
519      public static String formatTrace( final Tap tap, String message )
520        {
521        return TraceUtil.formatTrace( tap, message );
522        }
523    
524      /**
525       * @deprecated see {@link cascading.util.TraceUtil#formatTrace(cascading.scheme.Scheme, String)}
526       */
527      @Deprecated
528      public static String formatTrace( final Scheme scheme, String message )
529        {
530        return TraceUtil.formatTrace( scheme, message );
531        }
532    
533      /**
534       * @deprecated see {@link cascading.util.TraceUtil#formatTrace(cascading.operation.Operation, String)}
535       */
536      @Deprecated
537      public static String formatTrace( Operation operation, String message )
538        {
539        return TraceUtil.formatTrace( operation, message );
540        }
541    
542      public static void writeDOT( Writer writer, SimpleDirectedGraph graph, IntegerNameProvider vertexIdProvider, VertexNameProvider vertexNameProvider, EdgeNameProvider edgeNameProvider )
543        {
544        new DOTExporter( vertexIdProvider, vertexNameProvider, edgeNameProvider ).export( writer, graph );
545        }
546    
547      public static boolean isEmpty( String string )
548        {
549        return string == null || string.isEmpty();
550        }
551    
552      private static String[] findSplitName( String path )
553        {
554        String separator = "/";
555    
556        if( path.lastIndexOf( "/" ) < path.lastIndexOf( "\\" ) )
557          separator = "\\\\";
558    
559        String[] split = path.split( separator );
560    
561        path = split[ split.length - 1 ];
562    
563        path = path.substring( 0, path.lastIndexOf( '.' ) ); // remove .jar
564    
565        return path.split( "-(?=\\d)", 2 );
566        }
567    
568      public static String findVersion( String path )
569        {
570        if( path == null || path.isEmpty() )
571          return null;
572    
573        String[] split = findSplitName( path );
574    
575        if( split.length == 2 )
576          return split[ 1 ];
577    
578        return null;
579        }
580    
581      public static String findName( String path )
582        {
583        if( path == null || path.isEmpty() )
584          return null;
585    
586        String[] split = findSplitName( path );
587    
588        if( split.length == 0 )
589          return null;
590    
591        return split[ 0 ];
592        }
593    
594      public static long getSourceModified( Object confCopy, Iterator<Tap> values, long sinkModified ) throws IOException
595        {
596        long sourceModified = 0;
597    
598        while( values.hasNext() )
599          {
600          Tap source = values.next();
601    
602          if( source instanceof MultiSourceTap )
603            return getSourceModified( confCopy, ( (MultiSourceTap) source ).getChildTaps(), sinkModified );
604    
605          sourceModified = source.getModifiedTime( confCopy );
606    
607          // source modified returns zero if does not exist
608          // this should minimize number of times we touch any file meta-data server
609          if( sourceModified == 0 && !source.resourceExists( confCopy ) )
610            throw new FlowException( "source does not exist: " + source );
611    
612          if( sinkModified < sourceModified )
613            return sourceModified;
614          }
615    
616        return sourceModified;
617        }
618    
619      public static long getSinkModified( Object config, Collection<Tap> sinks ) throws IOException
620        {
621        long sinkModified = Long.MAX_VALUE;
622    
623        for( Tap sink : sinks )
624          {
625          if( sink.isReplace() || sink.isUpdate() )
626            sinkModified = -1L;
627          else
628            {
629            if( !sink.resourceExists( config ) )
630              sinkModified = 0L;
631            else
632              sinkModified = Math.min( sinkModified, sink.getModifiedTime( config ) ); // return youngest mod date
633            }
634          }
635        return sinkModified;
636        }
637    
638      public static String getTypeName( Type type )
639        {
640        if( type == null )
641          return null;
642    
643        return type instanceof Class ? ( (Class) type ).getCanonicalName() : type.toString();
644        }
645    
646      public static String getSimpleTypeName( Type type )
647        {
648        if( type == null )
649          return null;
650    
651        return type instanceof Class ? ( (Class) type ).getSimpleName() : type.toString();
652        }
653    
654      public static String[] typeNames( Type[] types )
655        {
656        String[] names = new String[ types.length ];
657    
658        for( int i = 0; i < types.length; i++ )
659          names[ i ] = getTypeName( types[ i ] );
660    
661        return names;
662        }
663    
664      public static String[] simpleTypeNames( Type[] types )
665        {
666        String[] names = new String[ types.length ];
667    
668        for( int i = 0; i < types.length; i++ )
669          names[ i ] = getSimpleTypeName( types[ i ] );
670    
671        return names;
672        }
673    
674      public static boolean containsNull( Object[] values )
675        {
676        for( Object value : values )
677          {
678          if( value == null )
679            return true;
680          }
681    
682        return false;
683        }
684    
685      public static void safeSleep( long durationMillis )
686        {
687        try
688          {
689          Thread.sleep( durationMillis );
690          }
691        catch( InterruptedException exception )
692          {
693          // do nothing
694          }
695        }
696    
697      /**
698       * Converts a given comma separated String of Exception names into a List of classes.
699       * ClassNotFound exceptions are ignored if no warningMessage is given, otherwise logged as a warning.
700       *
701       * @param classNames A comma separated String of Exception names.
702       * @return List of Exception classes.
703       */
704      public static Set<Class<? extends Exception>> asClasses( String classNames, String warningMessage )
705        {
706        Set<Class<? extends Exception>> exceptionClasses = new HashSet<Class<? extends Exception>>();
707        String[] split = classNames.split( "," );
708    
709        // possibly user provided type, load from context
710        ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
711    
712        for( String className : split )
713          {
714          if( className != null )
715            className = className.trim();
716    
717          if( isEmpty( className ) )
718            continue;
719    
720          try
721            {
722            Class<? extends Exception> exceptionClass = contextClassLoader.loadClass( className ).asSubclass( Exception.class );
723    
724            exceptionClasses.add( exceptionClass );
725            }
726          catch( ClassNotFoundException exception )
727            {
728            if( !Util.isEmpty( warningMessage ) )
729              LOG.warn( "{}: {}", warningMessage, className );
730            }
731          }
732    
733        return exceptionClasses;
734        }
735    
736      public interface RetryOperator<T>
737        {
738        T operate() throws Exception;
739    
740        boolean rethrow( Exception exception );
741        }
742    
743      public static <T> T retry( Logger logger, int retries, int secondsDelay, String message, RetryOperator<T> operator ) throws Exception
744        {
745        Exception saved = null;
746    
747        for( int i = 0; i < retries; i++ )
748          {
749          try
750            {
751            return operator.operate();
752            }
753          catch( Exception exception )
754            {
755            if( operator.rethrow( exception ) )
756              {
757              logger.warn( message + ", but not retrying", exception );
758    
759              throw exception;
760              }
761    
762            saved = exception;
763    
764            logger.warn( message + ", attempt: " + ( i + 1 ), exception );
765    
766            try
767              {
768              Thread.sleep( secondsDelay * 1000 );
769              }
770            catch( InterruptedException exception1 )
771              {
772              // do nothing
773              }
774            }
775          }
776    
777        logger.warn( message + ", done retrying after attempts: " + retries, saved );
778    
779        throw saved;
780        }
781    
782      public static Object createProtectedObject( Class type, Object[] parameters, Class[] parameterTypes )
783        {
784        try
785          {
786          Constructor constructor = type.getDeclaredConstructor( parameterTypes );
787    
788          constructor.setAccessible( true );
789    
790          return constructor.newInstance( parameters );
791          }
792        catch( Exception exception )
793          {
794          LOG.error( "unable to instantiate type: {}, with exception: {}", type.getName(), exception );
795    
796          throw new FlowException( "unable to instantiate type: " + type.getName(), exception );
797          }
798        }
799    
800      public static boolean hasClass( String typeString )
801        {
802        try
803          {
804          Util.class.getClassLoader().loadClass( typeString );
805    
806          return true;
807          }
808        catch( ClassNotFoundException exception )
809          {
810          return false;
811          }
812        }
813    
814      public static <T> T newInstance( String className, Object... parameters )
815        {
816        try
817          {
818          Class<T> type = (Class<T>) Util.class.getClassLoader().loadClass( className );
819    
820          return newInstance( type, parameters );
821          }
822        catch( ClassNotFoundException exception )
823          {
824          throw new CascadingException( "unable to load class: " + className, exception );
825          }
826        }
827    
828      public static <T> T newInstance( Class<T> target, Object... parameters )
829        {
830        // using Expression makes sure that constructors using sub-types properly work, otherwise we get a
831        // NoSuchMethodException.
832        Expression expr = new Expression( target, "new", parameters );
833    
834        try
835          {
836          return (T) expr.getValue();
837          }
838        catch( Exception exception )
839          {
840          throw new CascadingException( "unable to create new instance: " + target.getName() + "(" + Arrays.toString( parameters ) + ")", exception );
841          }
842        }
843    
844      public static Object invokeStaticMethod( String typeString, String methodName, Object[] parameters, Class[] parameterTypes )
845        {
846        try
847          {
848          Class type = Util.class.getClassLoader().loadClass( typeString );
849    
850          return invokeStaticMethod( type, methodName, parameters, parameterTypes );
851          }
852        catch( ClassNotFoundException exception )
853          {
854          throw new CascadingException( "unable to load class: " + typeString, exception );
855          }
856        }
857    
858      public static Object invokeStaticMethod( Class type, String methodName, Object[] parameters, Class[] parameterTypes )
859        {
860        try
861          {
862          Method method = type.getDeclaredMethod( methodName, parameterTypes );
863    
864          method.setAccessible( true );
865    
866          return method.invoke( null, parameters );
867          }
868        catch( Exception exception )
869          {
870          throw new CascadingException( "unable to invoke static method: " + type.getName() + "." + methodName, exception );
871          }
872        }
873    
874      public static Object invokeInstanceMethod( Object target, String methodName, Object[] parameters, Class[] parameterTypes )
875        {
876        try
877          {
878          Method method = target.getClass().getMethod( methodName, parameterTypes );
879    
880          method.setAccessible( true );
881    
882          return method.invoke( target, parameters );
883          }
884        catch( Exception exception )
885          {
886          throw new CascadingException( "unable to invoke instance method: " + target.getClass().getName() + "." + methodName, exception );
887          }
888        }
889    
890      public static <R> R returnInstanceFieldIfExistsSafe( Object target, String fieldName )
891        {
892        try
893          {
894          return returnInstanceFieldIfExists( target, fieldName );
895          }
896        catch( Exception exception )
897          {
898          // do nothing
899          return null;
900          }
901        }
902    
903      public static Object invokeConstructor( String className, Object[] parameters, Class[] parameterTypes )
904        {
905        try
906          {
907          Class type = Util.class.getClassLoader().loadClass( className );
908    
909          return invokeConstructor( type, parameters, parameterTypes );
910          }
911        catch( ClassNotFoundException exception )
912          {
913          throw new CascadingException( "unable to load class: " + className, exception );
914          }
915        }
916    
917      public static <T> T invokeConstructor( Class<T> target, Object[] parameters, Class[] parameterTypes )
918        {
919        try
920          {
921          Constructor<T> constructor = target.getConstructor( parameterTypes );
922    
923          constructor.setAccessible( true );
924    
925          return constructor.newInstance( parameters );
926          }
927        catch( Exception exception )
928          {
929          throw new CascadingException( "unable to create new instance: " + target.getName() + "(" + Arrays.toString( parameters ) + ")", exception );
930          }
931        }
932    
933      public static <R> R returnInstanceFieldIfExists( Object target, String fieldName )
934        {
935        try
936          {
937          Class<?> type = target.getClass();
938          Field field = getDeclaredField( fieldName, type );
939    
940          field.setAccessible( true );
941    
942          return (R) field.get( target );
943          }
944        catch( Exception exception )
945          {
946          throw new CascadingException( "unable to get instance field: " + target.getClass().getName() + "." + fieldName, exception );
947          }
948        }
949    
950      public static <R> void setInstanceFieldIfExists( Object target, String fieldName, R value )
951        {
952        try
953          {
954          Class<?> type = target.getClass();
955          Field field = getDeclaredField( fieldName, type );
956    
957          field.setAccessible( true );
958    
959          field.set( target, value );
960          }
961        catch( Exception exception )
962          {
963          throw new CascadingException( "unable to set instance field: " + target.getClass().getName() + "." + fieldName, exception );
964          }
965        }
966    
967      private static Field getDeclaredField( String fieldName, Class<?> type )
968        {
969        if( type == Object.class )
970          {
971          if( LOG.isDebugEnabled() )
972            LOG.debug( "did not find {} field on {}", fieldName, type.getName() );
973    
974          return null;
975          }
976    
977        try
978          {
979          return type.getDeclaredField( fieldName );
980          }
981        catch( NoSuchFieldException exception )
982          {
983          return getDeclaredField( fieldName, type.getSuperclass() );
984          }
985        }
986    
987      @Deprecated
988      public static String makeTempPath( String name )
989        {
990        if( name == null || name.isEmpty() )
991          throw new IllegalArgumentException( "name may not be null or empty " );
992    
993        name = cleansePathName( name.substring( 0, name.length() < 25 ? name.length() : 25 ) );
994    
995        return name + "/" + (int) ( Math.random() * 100000 ) + "/";
996        }
997    
998      public static String makePath( String prefix, String name )
999        {
1000        if( name == null || name.isEmpty() )
1001          throw new IllegalArgumentException( "name may not be null or empty " );
1002    
1003        if( prefix == null || prefix.isEmpty() )
1004          prefix = Long.toString( (long) ( Math.random() * 10000000000L ) );
1005    
1006        name = cleansePathName( name.substring( 0, name.length() < 25 ? name.length() : 25 ) );
1007    
1008        return prefix + "/" + name + "/";
1009        }
1010    
1011      public static String cleansePathName( String name )
1012        {
1013        return name.replaceAll( "\\s+|\\*|\\+|/+", "_" );
1014        }
1015      }