This question already has answers here:
Is there a way in Java to determine if a path is valid without attempting to create a file?
(7 answers)
Closed 7 years ago.
In my Java application I am renaming files to a file name provided in a String parameter. There is a method
boolean OKtoRename(String oldName, String newName)
which basically checks whether the newName isn't already taken by some other file, as I wouldn't want to bury existing ones.
It now occurred to me that perhaps the newName String will not denote a valid file name. So I thought to add this check to the method:
if (new File(newName).isFile()) {
return false;
}
Which obviously isn't the right way to do it, since in most cases the newFile does not yet exist and therefore although it is OKtoRename, the function returns false.
I was wondering, is there a method (I know there isn't for the java.io.File objects) like canExist()? Or would I have to resort to regex to make sure the newFile String does not contain invalid characters (e.g. ?, *, ", :)? I wonder if there is perhaps a function hidden somewhere in the JDK that would tell me if a string could possibly denote a valid file name.
I assembled a list of illegal filename characters (considering UNIX, Mac OS X and Windows systems) based on some online research a couple of months ago. If the new filename contains any of these, there's a risk that it might not be valid on all platforms.
private static final char[] ILLEGAL_CHARACTERS = { '/', '\n', '\r', '\t', '\0', '\f', '`', '?', '*', '\\', '<', '>', '|', '\"', ':' };
EDIT:
I would like to stress, that this is not a complete solution: as a commenter pointed out, even though it passes this test your file name could still be a Windows specific keyword like COM, PRN, etc. However, if your file name contains any of these characters, it will certainly cause trouble in a cross-platform environment.
Use createNewFile(), which will atomically create the file only if it doesn't yet exist.
If the file is created, the name is valid and it is not clobbering an existing file. You can then open the files and efficiently copy data from one to the other with FileChannel.transferXXX operations.
An important thing to keep in mind that, in general, the check and the creation should be atomic. If you first check whether an operation is safe, then perform the operation as a separate step, conditions may have changed in the meantime, making the operation unsafe.
Additional food for thought is available at this related post: "Move/Copy operations in Java."
Update:
Since this answer, the NIO.2 APIs have been introduced, which add more interaction with the file system.
Suppose you have an interactive program, and want to validate after each keystroke whether the file is potentially valid. For example, you might want to enable a "Save" button only when the entry is valid rather than popping up an error dialog after pressing "Save". Creating and ensuring the deletion of a lot of unnecessary files that my suggestion above would require seems like a mess.
With NIO.2, you can't create a Path instance containing characters that are illegal for the file system. An InvalidPathException is raised as soon as you try to create the Path.
However, there isn't an API to validate illegal names comprised of valid characters, like "PRN" on Windows. As a workaround, experimentation showed that using an illegal file name would raise a distinct exception when trying to access attributes (using Files.getLastModifiedTime(), for example).
If you specify a legal name for a file that does exist, you get no exception.
If you specify a legal name for a file that does not exist, it raises NoSuchFileException.
If you specify an illegal name, FileSystemException is raised.
However, this seems very kludgey and might not be reliable on other operating systems.
Here system specific way is suggested.
public static boolean isFilenameValid(String file) {
File f = new File(file);
try {
f.getCanonicalPath();
return true;
} catch (IOException e) {
return false;
}
}
If developing for Eclipse, check out org.eclipse.core.internal.resources.OS
public abstract class OS {
private static final String INSTALLED_PLATFORM;
public static final char[] INVALID_RESOURCE_CHARACTERS;
private static final String[] INVALID_RESOURCE_BASENAMES;
private static final String[] INVALID_RESOURCE_FULLNAMES;
static {
//find out the OS being used
//setup the invalid names
INSTALLED_PLATFORM = Platform.getOS();
if (INSTALLED_PLATFORM.equals(Platform.OS_WIN32)) {
//valid names and characters taken from http://msdn.microsoft.com/library/default.asp?url=/library/en-us/fileio/fs/naming_a_file.asp
INVALID_RESOURCE_CHARACTERS = new char[] {'\\', '/', ':', '*', '?', '"', '<', '>', '|'};
INVALID_RESOURCE_BASENAMES = new String[] {"aux", "com1", "com2", "com3", "com4", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$
"com5", "com6", "com7", "com8", "com9", "con", "lpt1", "lpt2", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$ //$NON-NLS-8$
"lpt3", "lpt4", "lpt5", "lpt6", "lpt7", "lpt8", "lpt9", "nul", "prn"}; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$ //$NON-NLS-8$ //$NON-NLS-9$
Arrays.sort(INVALID_RESOURCE_BASENAMES);
//CLOCK$ may be used if an extension is provided
INVALID_RESOURCE_FULLNAMES = new String[] {"clock$"}; //$NON-NLS-1$
} else {
//only front slash and null char are invalid on UNIXes
//taken from http://www.faqs.org/faqs/unix-faq/faq/part2/section-2.html
INVALID_RESOURCE_CHARACTERS = new char[] {'/', '\0',};
INVALID_RESOURCE_BASENAMES = null;
INVALID_RESOURCE_FULLNAMES = null;
}
}
/**
* Returns true if the given name is a valid resource name on this operating system,
* and false otherwise.
*/
public static boolean isNameValid(String name) {
//. and .. have special meaning on all platforms
if (name.equals(".") || name.equals("..")) //$NON-NLS-1$ //$NON-NLS-2$
return false;
if (INSTALLED_PLATFORM.equals(Platform.OS_WIN32)) {
//empty names are not valid
final int length = name.length();
if (length == 0)
return false;
final char lastChar = name.charAt(length-1);
// filenames ending in dot are not valid
if (lastChar == '.')
return false;
// file names ending with whitespace are truncated (bug 118997)
if (Character.isWhitespace(lastChar))
return false;
int dot = name.indexOf('.');
//on windows, filename suffixes are not relevant to name validity
String basename = dot == -1 ? name : name.substring(0, dot);
if (Arrays.binarySearch(INVALID_RESOURCE_BASENAMES, basename.toLowerCase()) >= 0)
return false;
return Arrays.binarySearch(INVALID_RESOURCE_FULLNAMES, name.toLowerCase()) < 0;
}
return true;
}
}
Just something i found, in java 7 and later, there is a class called Paths that has a method called get that takes one or more Strings and throws
InvalidPathException - if the path string cannot be converted to a Path
This is how I implemented this:
public boolean isValidFileName(final String aFileName) {
final File aFile = new File(aFileName);
boolean isValid = true;
try {
if (aFile.createNewFile()) {
aFile.delete();
}
} catch (IOException e) {
isValid = false;
}
return isValid;
}
To me it appears to be an OS dependent problem. You may simply want to check for some invalid character in the file name. Windows does this when you try to rename the file, it pops a message saying that a file cannot contain any of the following characters: \ / : * ? < > |
I am not sure if your question is "is there a library doing the job for me?" in that case I don't know any.
Using
String validName = URLEncoder.encode( fileName , "UTF-8");
File newFile = new File( validName );
Does the work.
I have just found today. I'm not sure if it works 100% of the time, but so far, I have been able to create valid file names.
Related
I'm using JavaParser to modify java source code. My goal is to read a single java source code file (ArithmeticClassToBeMutated) and store it in a compilation unit. Then, i'd like to replace/mutate its arithmetic operators (PLUS,MINUS,MULTIPLY,DIVISION,REMAINDER). All instances of an operator (e.g. plus) shall always be replaced with another one (e.g. minus). In the end, i want to have 4 output files:
One java source code file where every "Plus" became a "Minus",
one file where every "Plus" became a "Multiply",
one file where every "Plus" became a "Division", and
one file where every "Plus" became a "Remainder/Modulo). I can't type the symbols or else i get a formatting error.
In my code (see below), the replacement/modification itself works. Now, my question is: how can I change the name of the output source code files? I did it with the methods add and saveAll:
sourceRoot.add("", OperatorToBeMutated.name() + "_TO_" + arithmeticOperators[i].name() + "_MUTATED_"
+ cu.getStorage().get().getFileName(), cu);
sourceRoot.saveAll(
CodeGenerationUtils.mavenModuleRoot(ReplacementAO.class).resolve(Paths.get("output")));
However, this creates two output files for each operator replacement. One file has the same name as the input file, and one file has my naming convention. The content is the same. What can I do to only save a single file (with my own naming) for each loop? Specifying no name would result in the output file overwriting itself with each iteration, as the name stays the same.
Thank you!
public static String filename = "ArithmeticClassToBeMutated.java";
public static void main(String[] args) {
for (i = 0; i < arithmeticOperators.length; i++) {
if (arithmeticOperators[i] == OperatorToBeMutated) {
continue;
}
sourceRoot = new SourceRoot(
CodeGenerationUtils.mavenModuleRoot(ReplacementAO.class).resolve("src/main/resources"));
CompilationUnit cu = sourceRoot.parse("", filename);
cu.accept(new ModifierVisitor<Void>() {
#Override
public Visitable visit(BinaryExpr n, Void arg) {
if (n.getOperator() == OperatorToBeMutated && n.getLeft().isNameExpr()
&& n.getRight().isNameExpr()) {
n.setOperator(arithmeticOperators[i]);
comment.setContent("Here, the operator " + OperatorToBeMutated.name()
+ " was mutated with the operator " + arithmeticOperators[i].name() + ".");
n.setComment(comment);
}
return super.visit(n, arg);
}
}, null);
sourceRoot.add("", OperatorToBeMutated.name() + "_TO_" + arithmeticOperators[i].name() + "_MUTATED_"
+ cu.getStorage().get().getFileName(), cu);
sourceRoot.saveAll(
CodeGenerationUtils.mavenModuleRoot(ReplacementAO.class).resolve(Paths.get("output")));
}
}
You wouldn't want to rename your file name to be different from the class name, as a public java class needs to have the same name as its file name. As far as I am aware this will throw a compiler error for public classes:
Can I compile a java file with a different name than the class?
I would suggest putting the mutated classes into different folders. Just adding another directory at the end of your path will automatically create a new folder. So for your example:
sourceRoot.saveAll(CodeGenerationUtils.mavenModuleRoot(LogicalOperators.class).resolve(Paths.get("output" + "/mutation1")));
I have a very simple question and just can't get my code to work in Java. I want to return a string that indicates a filepath, but extracts a certain portion of that path if it it exists.
So, my input might be "c:/lotus/notes/data/directory/mydatabase.nsf" and I want to return only "directory/mydatabase.nsf". Sometimes, the path provided will already leave out the "c:/lotus/notes/data/" because it is being accessed on the server rather than locally.
public String getDataPath ( String path ) {
int pathStart;
boolean pathContains;
String lowerPath;
lowerPath = path.toLowerCase();
pathStart = lowerPath.indexOf("c:/lotus/notes/data");
if ( pathStart >= 0) {
// 20 characters in "c:/lotus/notes/data/"
return path.substring(19);
}
pathContains = lowerPath.contains("lotus/notes/data");
if ( pathContains ) {
// 20 characters in "c:/lotus/notes/data/"
return path.substring(19);
}
return path;
}
This is simple, but somehow, I can't get it right. Neither of my if's ever evaluates as true.
Keep it simple:
path.replace("c:/lotus/notes/data", "");
You can simply do a path.replaceAll("c:/lotus/notes/data", ""). This will remove the leading path name if it is contained in the string, else the string will not change.
Take a look at the Path interface:
Paths Documentation
Path Documentation
It is used to do exactly what you want: Extract informations about the single parts of a path.
Using Java 1.6 Filepath can be entered by user and then I apply various regular expressions to remove characters that are invalid for the platform (such as '?' is invalid on Windows), and check path length to ensure we end up with a valid filepath for the OS before trying to create the filepath.
But there are two problems:
Its a pain working out what is valid or not for each platform.
I'm making assumptions based on default filesystem for the platform, but of course an OSX system could be writing to a non-mac filesystem such a FAT32, in which case these checks will not be valid.
So I was hoping there would be a better way to do it with NIO2 in Java 7, but haven't found a solution yet, is there one ?
Depending on your expected result (corrected String? Invalid character position? Exception?), this should give you an idea of what can be done:
import java.io.File;
import java.nio.file.InvalidPathException;
public class Test {
public static final void main(final String[] args) {
final String current = new File(".").toPath().toAbsolutePath().normalize().toFile().toString();
Test.correctPath(current);
Test.correctPath(current + "aValidExpression");
Test.correctPath(current + "aValidExpression?;:-&é");
Test.correctPath(current + "aValidExpr//ession?;:-&é");
Test.correctPath(current + "aValidExpre\\ssion?;:-&é");
}
public static final String correctPath(final String path) {
try {
final String returnValue = new File(path).toPath().toAbsolutePath().normalize().toFile().toString();
System.out.println(returnValue);
return returnValue;
} catch (final InvalidPathException e) {
System.err.println(e.getMessage());
final int errorIndex = e.getIndex();
final String newPath = path.substring(0, errorIndex - 1) + path.substring(errorIndex + 1);
return Test.correctPath(newPath);
}
}
}
I hope it helps.
The key to your question is the phrase "remove characters that are invalid for the platform". The various String to Path conversion functions, such as get() and resolve(), will tell you whether the string was valid as a path, but the won't tell why it's invalid. One way of being invalid is to contain invalid characters. Another would be to have, say, too many slash characters together. Regardless, the library does not give any more information than this; it provides no facility to assist in validating user input in any way that would help a user fix an input error. This ought to be a standard practice, admittedly, but it's hardly a practice at all.
Upshot: You'll have to write such a validation library yourself if you want to have one. Upside: You certainly aren't the only person with such a problem.
I guess you should look at Path.getPath
public static Path get(String first,
String... more)
getPath("/foo","bar","gus")-->/foo/bar/gus
Converts a path string, or a sequence of strings that when joined form a path string, to a Path. If more does not specify any elements then the value of the first parameter is the path string to convert. If more specifies one or more elements then each non-empty string, including first, is considered to be a sequence of name elements (see Path) and is joined to form a path string. The details as to how the Strings are joined is provider specific but typically they will be joined using the name-separator as the separator. For example, if the name separator is "/" and getPath("/foo","bar","gus") is invoked, then the path string "/foo/bar/gus" is converted to a Path. A Path representing an empty path is returned if first is the empty string and more does not contain any non-empty strings.
This question already has answers here:
How to get the filename without the extension in Java?
(22 answers)
Closed 9 years ago.
I am trying to get name of a File object without its extension, e.g. getting "vegetation" when the filename is "vegetation.txt." I have tried implementing this code:
openFile = fileChooser.getSelectedFile();
String[] tokens = openFile.getName().split(".");
String name = tokens[0];
Unfortunately, it returns a null object. There is a problem just in the defining the String object, I guess, because the method getName() works correctly; it gives me the name of the file with its extension.
Do you have any idea?
If you want to implement this yourself, try this:
String name = file.getName();
int pos = name.lastIndexOf(".");
if (pos > 0) {
name = name.substring(0, pos);
}
(This variation doesn't leave you with an empty string for an input filename like ".txt". If you want the string to be empty in that case, change > 0 to >= 0.)
You could replace the if statement with an assignment using a conditional expression, if you thought it made your code more readable; see #Steven's answer for example. (I don't think it does ... but it is a matter of opinion.)
It is arguably a better idea to use an implementation that someone else has written and tested. Apache FilenameUtils is a good choice; see #slachnick's Answer, and also the linked Q&A.
If you don't want to write this code yourself you could use Apache's FilenameUtils.
FilenameUtils.getBaseName(openFile.getName());
This will return the filename minus the path and extension.
I prefer to chop off before the last index of "." to be the filename.
This way a file name: hello.test.txt is just hello.test
i.e.
int pos = filename.lastIndexOf(".");
String justName = pos > 0 ? filename.substring(0, pos) : filename;
You need to handle there being no extension too.
String#split takes a regex. "." matches any character, so you're getting an array of empty strings - one for each spot in between each pair of characters.
http://docs.oracle.com/javase/6/docs/api/java/lang/String.html#split(java.lang.String)
There are two problems with your code...
1) Just using "." as an argument to split is being interpreted as a Rejex that you don't want. You want a literal dot. So you have to escape it...
openFile.getName().split("\\.");
2) You will incorrectly parse any file with more than one dot. The best way to do this is to search for the last dot and get the substring...
int pos = openFile.getName().lastIndexOf(".");
if(pos != -1) {
String name = openFile.getName().substring(0, pos);
}
You can try split("\\.");. That is, basically escaping the . as it's treated as all characters in regex.
This is the input as string:
"C:\jdk1.6.0\bin\program1.java"
I need output as:
Path-->C:\jdk1.6.0\bin\
file--->program1.java
extension--->.java
Watch out the "\" char. I easily got output for "/".
The File class gives you everything you need:
File f = new File("C:\\jdk1.6.0\\bin\\program1.java");
System.out.println("Path-->" + f.getParent());
System.out.println("file--->" + f.getName());
int idx = f.getName().lastIndexOf('.');
System.out.println("extension--->" + ((idx > 0) ? f.getName().substring(idx) : "") );
EDIT: Thanks Dave for noting that String.lastIndexOf will return -1 if File.getName does not contain '.'.
Consider using an existing solution instead of rolling your own and introducing more code that needs to be tested. FilenameUtils from Apache Commons IO is one example:
http://commons.apache.org/proper/commons-io/javadocs/api-2.4/org/apache/commons/io/FilenameUtils.html
Since Java's File class does not support probing for the extension, I suggest you create a subclass of File that provides this ability:
package mypackage;
/**
* Enhances java.io.File functionality by adding extension awareness.
*/
public class File extends java.io.File {
/**
* Returns the characters after the last period.
*
* #return An empty string if there is no extension.
*/
public String getExtension() {
String name = getName();
String result = "";
int index = name.lastIndexOf( '.' );
if( index > 0 ) {
result = name.substring( index );
}
return result;
}
}
Now simply substitute your version of File for Java's version and, when combined with Kurt's answer, gives you everything you need.
Notice that using a subclass is ideal because if you wanted to change the behaviour (due to a different operating system using a different extension delimiter token), you need only update a single method and your entire application continues to work. (Or if you need to fix a bug, such as trying to execute str.substring( -1 ).)
In other words, if you extract a file extension in more than one place in your code base, you have made a mistake.
Going further, if you wanted to completely abstract the knowledge of the file type (because some operating systems might not use the . separator), you could write:
/**
* Enhances java.io.File functionality by adding extension awareness.
*/
public class File extends java.io.File {
public File( String filename ) {
super( filename );
}
/**
* Returns true if the file type matches the given type.
*/
public boolean isType( String type ) {
return getExtension().equals( type );
}
/**
* Returns the characters after the last period.
*
* #return An empty string if there is no extension.
*/
private String getExtension() {
String name = getName();
String result = "";
int index = name.lastIndexOf( '.' );
if( index > 0 ) {
result = name.substring( index );
}
return result;
}
}
I would consider this a much more robust solution. This would seamlessly allow substituting a more advanced file type detection mechanism (analysis of file contents to determine the type), without having to change the calling code. Example:
File file = new File( "myfile.txt" );
if( file.isType( "png" ) ) {
System.out.println( "PNG image found!" );
}
If a user saved "myfile.png" as "myfile.txt", the image would still be processed because the advanced version (not shown here) would look for the "PNG" marker that starts every single PNG file in the (cyber) world.
You need to compensate for the double slashes returned in Path (if it has been programmatically generated).
//Considering that strPath holds the Path String
String[] strPathParts = strPath.split("\\\\");
//Now to check Windows Drive
System.out.println("Drive Name : "+strPathParts[0]);