/****************************************************************************** * Copyright (c) 2016 Oracle * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Konstantin Komissarchik - initial implementation and ongoing maintenance ******************************************************************************/ package org.eclipse.sapphire; import java.util.Set; import org.eclipse.sapphire.util.SetFactory; /** * Represents the name of a file within a folder. * * <p>Ready to be used as a type of a value property. The following services are provided:</p> * * <ol> * * <li>ValidationService - Flags invalid file names as errors.</p> * * <li>ValueSerializationService - Only creates FileName objects that are valid for * the current platform.</p> * * <li>ValueNormalizationService</li> * * <ol type="A"> * * <li>Leading whitespace is removed.</li> * * <li>Trailing whitespace and dots are removed.</li> * * <li>Extension is added if file name does not have one already and if the property * has a FileExtensionsService (usually via @FileExtensions annotation).</li> * * </ol> * * </ol> * * @author <a href="mailto:konstantin.komissarchik@oracle.com">Konstantin Komissarchik</a> */ public final class FileName implements Comparable<FileName> { private static final boolean WINDOWS; private static final Set<Character> INVALID_CHARACTERS; private static final Set<String> INVALID_BASENAMES; private static final Set<String> INVALID_FULLNAMES; static { WINDOWS = System.getProperties().getProperty( "os.name" ).startsWith( "Windows" ); final SetFactory<Character> invalidCharactersSetFactory = SetFactory.start(); final SetFactory<String> invalidBaseNameSetFactory = SetFactory.start(); final SetFactory<String> invalidFullNameSetFactory = SetFactory.start(); for( int i = 0; i <= 31; i++ ) { invalidCharactersSetFactory.add( (char) i ); } if( WINDOWS ) { invalidCharactersSetFactory.add( '\\' ); invalidCharactersSetFactory.add( '/' ); invalidCharactersSetFactory.add( ':' ); invalidCharactersSetFactory.add( '*' ); invalidCharactersSetFactory.add( '?' ); invalidCharactersSetFactory.add( '"' ); invalidCharactersSetFactory.add( '<' ); invalidCharactersSetFactory.add( '>' ); invalidCharactersSetFactory.add( '|' ); invalidBaseNameSetFactory.add( "aux" ); invalidBaseNameSetFactory.add( "com1" ); invalidBaseNameSetFactory.add( "com2" ); invalidBaseNameSetFactory.add( "com3" ); invalidBaseNameSetFactory.add( "com4" ); invalidBaseNameSetFactory.add( "com5" ); invalidBaseNameSetFactory.add( "com6" ); invalidBaseNameSetFactory.add( "com7" ); invalidBaseNameSetFactory.add( "com8" ); invalidBaseNameSetFactory.add( "com9" ); invalidBaseNameSetFactory.add( "con" ); invalidBaseNameSetFactory.add( "lpt1" ); invalidBaseNameSetFactory.add( "lpt2" ); invalidBaseNameSetFactory.add( "lpt3" ); invalidBaseNameSetFactory.add( "lpt4" ); invalidBaseNameSetFactory.add( "lpt5" ); invalidBaseNameSetFactory.add( "lpt6" ); invalidBaseNameSetFactory.add( "lpt7" ); invalidBaseNameSetFactory.add( "lpt8" ); invalidBaseNameSetFactory.add( "lpt9" ); invalidBaseNameSetFactory.add( "nul" ); invalidBaseNameSetFactory.add( "prn" ); invalidFullNameSetFactory.add( "clock$" ); } else { invalidCharactersSetFactory.add( '/' ); } INVALID_CHARACTERS = invalidCharactersSetFactory.result(); INVALID_BASENAMES = invalidBaseNameSetFactory.result(); INVALID_FULLNAMES = invalidFullNameSetFactory.result(); } private final String full; private final String base; private final String extension; /** * Constructs a new FileName object. * * @param name file name as a string * @throws IllegalArgumentException if file name is invalid for the current platform */ public FileName( final String name ) { if( ! valid( name ) ) { throw new IllegalArgumentException( name ); } // Note that the rest of the code in the constructor is able to avoid handling various // corner cases because they are ruled out by valid() call above. this.full = name; int segments = 0; for( String segment : name.split( "\\." ) ) { if( segment.trim().length() > 0 ) { segments++; if( segments > 1 ) { break; // Only need to know if count is anything other than one. } } } if( segments == 1 ) { this.base = name; this.extension = null; } else { final int lastDot = name.lastIndexOf( '.' ); this.base = name.substring( 0, lastDot ); this.extension = name.substring( lastDot + 1 ); } } /** * Returns the full file name, including the base portion and extension. * * @return the full file name, including the base portion and extension. */ public String full() { return this.full; } /** * Returns the portion of the file name in front of extension. * * @return the portion of the file name in front of extension */ public String base() { return this.base; } /** * Returns the file extension. A file extension is defined as the last significant segment * of a file name that has at least two significant segments. The significant segments are * derived by splitting the full file name on dots and discarding all zero-length segments * as well as those segments composed entirely of whitespace. * * <p>For instance, file name "config.xml" has extension "xml" while file name ".config" * has no extension.</p> * * @return the file extension or null */ public String extension() { return this.extension; } @Override public int hashCode() { return this.full.hashCode(); } @Override public boolean equals( final Object obj ) { if( obj instanceof FileName ) { return this.full.equals( ( (FileName) obj ).full ); } return false; } public int compareTo( final FileName fname ) { return this.full.compareToIgnoreCase( fname.full ); } @Override public String toString() { return this.full; } public static boolean valid( final String name ) { if( name == null || name.length() == 0 ) { return false; } final int length = name.length(); if( length == 0 ) { return false; } if( name.equals( "." ) || name.equals( ".." ) ) { return false; } for( int i = 0; i < length; i++ ) { if( INVALID_CHARACTERS.contains( name.charAt( i ) ) ) { return false; } } if( WINDOWS ) { final char lastChar = name.charAt( name.length() - 1 ); if( lastChar == '.' || Character.isWhitespace( lastChar ) ) { return false; } final int dot = name.indexOf( '.' ); final String basename = ( dot == -1 ? name : name.substring( 0, dot ) ); if( INVALID_BASENAMES.contains( basename.toLowerCase() ) ) { return false; } if( INVALID_FULLNAMES.contains( name.toLowerCase() ) ) { return false; } } return true; } }