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