I'm learning design patterns and at this stage, I'm playing around with creational patterns, Factory Method to be precise.
The idea is to have a Factory Method to create either documents or links(symlinks) in a "virtual" filesystem.
Would someone be willing to take a look at the code and advice me and guide me to the right path ?
FileFactory.java
public final class FileFactory {
public FileFactory(){}
public static IFileFactory createSoftLink(){
return new SymbolicLinkFactory();
}
public static IFileFactory createDocument(){
return new DocumentFileFactory();
}
Interface IFileFactory.java
public interface IFileFactory {
FileSystemElement createFile (String name, String mimeType, String currentDirectory, String user) throws IOException;}
DocumentFileFactory.java
public class DocumentFileFactory implements IFileFactory {
protected DocumentFileFactory() {}
#Override
public mFile createFile (String name, String mimeType, String currentDirectory, String user) throws IOException {
String fName = name;
if (mimeType.equalsIgnoreCase("docx")) {
fName += ".docx";
}else if (mimeType.equalsIgnoreCase("pptx")) {
fName += ".pptx";
}else if (mimeType.equalsIgnoreCase("xlsx")) {
fName += ".xlsx";
}else if (mimeType.equalsIgnoreCase("docm")) {
fName += ".docm";
}else if (mimeType.equalsIgnoreCase("pptm")) {
fName += ".pptm";
}else if (mimeType.equalsIgnoreCase("xlxm")) {
fName += ".xlxm";
}else {
fName += "."+mimeType;
}
mFile file = new mFile();
file.rename(fName);
file.create(fName, currentDirectory, user);
Path filePath = Paths.get(currentDirectory+System.getProperty("file.separator")+fName);
file.setPath(filePath);
return file;
}
SymbolicLinkFactory.java
public class SymbolicLinkFactory implements IFileFactory {
private FileSystemElement fsElement;
protected SymbolicLinkFactory() {}
#Override
public FileSystemElement createFile(String name, String mimeType, String currentDirectory, String user) throws IOException {
SoftLink sl = new SoftLink(name, fsElement, fsElement.getPath(), Paths.get(currentDirectory));
return sl;
}
public void setFileSystemElement(FileSystemElement fsElement) {
this.fsElement = fsElement;
}
In general it looks fine to me. It would be better if you had some logic that showd why you would use the factory pattern, for example something that checks if the document you want to create already exists, and returns a softlink creator if it does.
As mentioned by Josh, the implementation of createFile is a bit confusing, and diverts attention from your problem/goal.
As the code stands, there is no need for the FileFactory class. All it does is to centralize/shortcut to your implementations of the IFileFactory interface.
There are two other common use cases for factory class that might be relevant to your situation:
Dependency injection: Hide which implementation is actually used
inside the FileFactory class, allow to switch implementation at
startup/runtime
Factory/class families: Here you are only creating
one instance. Imagine instead that you had two kinds of items - file
item and folder. In that case it's important that a "file item" you
create is of the same kind as your "folder", for exampl eso a file
system file goes in a file system folder, and a Wiki page goes in a
Wiki URL.
Related
I need to get a list of all caller methods for a method of interest for me in Java. Is there a tool that can help me with this?
Edit: I forgot to mention that I need to do this from a program. I'm usig Java Pathfinder and I want to run it an all the methods that call my method of interest.
For analyzing bytecode, I would recommend ASM. Given a list of Classes to analyze, a visitor can be made which finds the method calls you're interested in. One implementation which analyses classes in a jar file is below.
Note that ASM uses internalNames with '/' instead of '.' as a separator. Specify the target method as a standard declaration without modifiers.
For example, to list methods that could be calling System.out.println("foo") in the java runtime jar:
java -cp "classes;asm-3.1.jar;asm-commons-3.1.jar" App \
c:/java/jdk/jre/lib/rt.jar \
java/io/PrintStream "void println(String)"
Edit: source and line numbers added: Note that this only indicates the last target method invocation per calling method - the original q only wanted to know which methods. I leave it as an exercise for the reader to show line numbers of the calling method declaration, or the line numbers of every target invocation, depending on what you're actually after. :)
results in:
LogSupport.java:44 com/sun/activation/registries/LogSupport log (Ljava/lang/String;)V
LogSupport.java:50 com/sun/activation/registries/LogSupport log (Ljava/lang/String;Ljava/lang/Throwable;)V
...
Throwable.java:498 java/lang/Throwable printStackTraceAsCause (Ljava/io/PrintStream;[Ljava/lang/StackTraceElement;)V
--
885 methods invoke java/io/PrintStream println (Ljava/lang/String;)V
source:
public class App {
private String targetClass;
private Method targetMethod;
private AppClassVisitor cv;
private ArrayList<Callee> callees = new ArrayList<Callee>();
private static class Callee {
String className;
String methodName;
String methodDesc;
String source;
int line;
public Callee(String cName, String mName, String mDesc, String src, int ln) {
className = cName; methodName = mName; methodDesc = mDesc; source = src; line = ln;
}
}
private class AppMethodVisitor extends MethodAdapter {
boolean callsTarget;
int line;
public AppMethodVisitor() { super(new EmptyVisitor()); }
public void visitMethodInsn(int opcode, String owner, String name, String desc) {
if (owner.equals(targetClass)
&& name.equals(targetMethod.getName())
&& desc.equals(targetMethod.getDescriptor())) {
callsTarget = true;
}
}
public void visitCode() {
callsTarget = false;
}
public void visitLineNumber(int line, Label start) {
this.line = line;
}
public void visitEnd() {
if (callsTarget)
callees.add(new Callee(cv.className, cv.methodName, cv.methodDesc,
cv.source, line));
}
}
private class AppClassVisitor extends ClassAdapter {
private AppMethodVisitor mv = new AppMethodVisitor();
public String source;
public String className;
public String methodName;
public String methodDesc;
public AppClassVisitor() { super(new EmptyVisitor()); }
public void visit(int version, int access, String name,
String signature, String superName, String[] interfaces) {
className = name;
}
public void visitSource(String source, String debug) {
this.source = source;
}
public MethodVisitor visitMethod(int access, String name,
String desc, String signature,
String[] exceptions) {
methodName = name;
methodDesc = desc;
return mv;
}
}
public void findCallingMethodsInJar(String jarPath, String targetClass,
String targetMethodDeclaration) throws Exception {
this.targetClass = targetClass;
this.targetMethod = Method.getMethod(targetMethodDeclaration);
this.cv = new AppClassVisitor();
JarFile jarFile = new JarFile(jarPath);
Enumeration<JarEntry> entries = jarFile.entries();
while (entries.hasMoreElements()) {
JarEntry entry = entries.nextElement();
if (entry.getName().endsWith(".class")) {
InputStream stream = new BufferedInputStream(jarFile.getInputStream(entry), 1024);
ClassReader reader = new ClassReader(stream);
reader.accept(cv, 0);
stream.close();
}
}
}
public static void main( String[] args ) {
try {
App app = new App();
app.findCallingMethodsInJar(args[0], args[1], args[2]);
for (Callee c : app.callees) {
System.out.println(c.source+":"+c.line+" "+c.className+" "+c.methodName+" "+c.methodDesc);
}
System.out.println("--\n"+app.callees.size()+" methods invoke "+
app.targetClass+" "+
app.targetMethod.getName()+" "+app.targetMethod.getDescriptor());
} catch(Exception x) {
x.printStackTrace();
}
}
}
Edit: the original question was edited to indicate a runtime solution was needed - this answer was given before that edit and only indicates how to do it during development.
If you are using Eclipse you can right click the method and choose "Open call hierarchy" to get this information.
Updated after reading comments: Other IDEs support this as well in a similar fashion (at least Netbeans and IntelliJ do)
Annotate the method with #Deprecated ( or tag it with #deprecated ), turn on deprecation warnings, run your compile and see which warnings get triggered.
The run your compile bit can be done either by invoking an external ant process or by using the Java 6 compiler API.
right click on method
Go to references and (depending on your requirement)
choose workspace/project/Hierarchy.
This pops up a panel that shows all references to this functions. Eclipse FTW !
In eclipse, highlight the method name and then Ctrl+Shift+G
There isn't a way to do this (programmatically) via the Java reflection libraries - you can't ask a java.lang.reflect.Method "which methods do you call?"
That leaves two other options I can think of:
Static analysis of the source code. I'm sure this is what the Eclipse Java toolset does - you could look at the Eclipse source behind the JDT, and find what it does when you ask Eclipse to "Find References" to a method.
Bytecode analysis. You could inspect the bytecode for calls to the method. I'm not sure what libraries or examples are out there to help with this - but I can't imagine that something doesn't exist.
Yes, most modern IDE:s will let you either search for usages of a method or variable. Alternatively, you could use a debugger and set a trace point on the method entry, printing a stack trace or whatever every time the method is invoked.
Finally, you could use some simple shell util to just grep for the method, such as
find . -name '*.java' -exec grep -H methodName {} ;
The only method that will let you find invokations made through some reflection method, though, would be using the debugger.
I made a small example using #Chadwick's one. It's a test that assesses if calls to getDatabaseEngine() are made by methods that implement #Transaction.
/**
* Ensures that methods that call {#link DatabaseProvider#getDatabaseEngine()}
* implement the {#link #Transaction} annotation.
*
* #throws Exception If something occurs while testing.
*/
#Test
public void ensure() throws Exception {
final Method method = Method.getMethod(
DatabaseEngine.class.getCanonicalName() + " getDatabaseEngine()");
final ArrayList<java.lang.reflect.Method> faultyMethods = Lists.newArrayList();
for (Path p : getAllClasses()) {
try (InputStream stream = new BufferedInputStream(Files.newInputStream(p))) {
ClassReader reader = new ClassReader(stream);
reader.accept(new ClassAdapter(new EmptyVisitor()) {
#Override
public MethodVisitor visitMethod(final int access, final String name, final String desc, final String signature, final String[] exceptions) {
return new MethodAdapter(new EmptyVisitor()) {
#Override
public void visitMethodInsn(int opcode, String owner, String nameCode, String descCode) {
try {
final Class<?> klass = Class.forName(Type.getObjectType(owner).getClassName());
if (DatabaseProvider.class.isAssignableFrom(klass) &&
nameCode.equals(method.getName()) &&
descCode.equals(method.getDescriptor())) {
final java.lang.reflect.Method method = klass.getDeclaredMethod(name,
getParameters(desc).toArray(new Class[]{}));
for (Annotation annotation : method.getDeclaredAnnotations()) {
if (annotation.annotationType().equals(Transaction.class)) {
return;
}
}
faultyMethods.add(method);
}
} catch (Exception e) {
Throwables.propagate(e);
}
}
};
}
}, 0);
}
}
if (!faultyMethods.isEmpty()) {
fail("\n\nThe following methods must implement #Transaction because they're calling getDatabaseEngine().\n\n" + Joiner.on("\n").join
(faultyMethods) + "\n\n");
}
}
/**
* Gets all the classes from target.
*
* #return The list of classes.
* #throws IOException If something occurs while collecting those classes.
*/
private List<Path> getAllClasses() throws IOException {
final ImmutableList.Builder<Path> builder = new ImmutableList.Builder<>();
Files.walkFileTree(Paths.get("target", "classes"), new SimpleFileVisitor<Path>() {
#Override
public FileVisitResult visitFile(final Path file, final BasicFileAttributes attrs) throws IOException {
if (file.getFileName().toString().endsWith(".class")) {
builder.add(file);
}
return FileVisitResult.CONTINUE;
}
});
return builder.build();
}
/**
* Gets the list of parameters given the description.
*
* #param desc The method description.
* #return The list of parameters.
* #throws Exception If something occurs getting the parameters.
*/
private List<Class<?>> getParameters(String desc) throws Exception {
ImmutableList.Builder<Class<?>> obj = new ImmutableList.Builder<>();
for (Type type : Type.getArgumentTypes(desc)) {
obj.add(ClassUtils.getClass(type.getClassName()));
}
return obj.build();
}
1)In eclipse it is ->right click on the method and select open call hierarchy or CLT+ALT+H
2)In jdeveloper it is -> right click on the method and select calls or ALT+SHIFT+H
The closest that I could find was the method described in this StackOverflow questions selected answer.check this out
You can do this with something in your IDE such as "Find Usages" (which is what it is called in Netbeans and JDeveloper). A couple of things to note:
If your method implements a method from an interface or base class, you can only know that your method is POSSIBLY called.
A lot of Java frameworks use Reflection to call your method (IE Spring, Hibernate, JSF, etc), so be careful of that.
On the same note, your method could be called by some framework, reflectively or not, so again be careful.
I have a library which parse URLs and extract some data. There is one class per URL. To know which class should handle the URL provided by the user, I have the code below.
public class HostExtractorFactory {
private HostExtractorFactory() {
}
public static HostExtractor getHostExtractor(URL url)
throws URLNotSupportedException {
String host = url.getHost();
switch (host) {
case HostExtractorABC.HOST_NAME:
return HostExtractorAbc.getInstance();
case HostExtractorDEF.HOST_NAME:
return HostExtractorDef.getInstance();
case HostExtractorGHI.HOST_NAME:
return HostExtractorGhi.getInstance();
default:
throw new URLNotSupportedException(
"The url provided does not have a corresponding HostExtractor: ["
+ host + "]");
}
}
}
The problem is users are requesting more URL to be parsed, which means my switch statement is growing. Every time someone comes up with a parser, I have to modify my code to include it.
To end this, I've decided to create a map and expose it to them, so that when their class is written, they can register themselves to the factory, by providing the host name, and the extractor to the factory. Below is the factory with this idea implemented.
public class HostExtractorFactory {
private static final Map<String, HostExtractor> EXTRACTOR_MAPPING = new HashMap<>();
private HostExtractorFactory() {
}
public static HostExtractor getHostExtractor(URL url)
throws URLNotSupportedException {
String host = url.getHost();
if(EXTRACTOR_MAPPING.containsKey(host)) {
return EXTRACTOR_MAPPING.get(host);
} else {
throw new URLNotSupportedException(
"The url provided does not have a corresponding HostExtractor: ["
+ host + "]");
}
}
public static void register(String hostname, HostExtractor extractor) {
if(StringUtils.isBlank(hostname) == false && extractor != null) {
EXTRACTOR_MAPPING.put(hostname, extractor);
}
}
}
And the user would use it that way:
public class HostExtractorABC extends HostExtractor {
public final static String HOST_NAME = "www.abc.com";
private static class HostPageExtractorLoader {
private static final HostExtractorABC INSTANCE = new HostExtractorABC();
}
private HostExtractorABC() {
if (HostPageExtractorLoader.INSTANCE != null) {
throw new IllegalStateException("Already instantiated");
}
HostExtractorFactory.register(HOST_NAME, this);
}
public static HostExtractorABC getInstance() {
return HostPageExtractorLoader.INSTANCE;
}
...
}
I was patting my own back when I realized this will never work: the user classes are not loaded when I receive the URL, only the factory, which means their constructor never runs, and the map is always empty. So I am back to the drawing board, but would like some ideas around getting this to work or another approach to get rid of this pesky switch statement.
S
Another option is to use the Service Loader approach.
Having your implementers add something like the following in ./resources/META-INF/services/your.package.HostExtractor:
their.package1.HostExtractorABC
their.package2.HostExtractorDEF
their.package3.HostExtractorGHI
...
Then in your code, you can have something like:
HostExtractorFactory() {
final ServiceLoader<HostExtractor> loader
= ServiceLoader.load(your.package.HostExtractor.class);
for (final HostExtractor registeredExtractor : loader) {
// TODO - Perform pre-processing which is required.
// Add to Map? Extract some information and store? Etc.
}
}
I would advice for you to learn about dependency injection (I love spring implementation). You will then be able to write an interface like
public interface HostExtractorHandler {
public String getName();
public HostExtractor getInstance();
}
Than your code can "ask" for all classes that implements this interface, you then would be able to build your map in the initialization phase of your class.
I would use the Reflections library to locate the parsers. They all appear to derive from the HostExtractor class, so use the library to locate all subtypes:
Reflections reflections = new Reflections("base.package");
Set<Class<? extends HostExtractor>> extractorTypes =
reflections.getSubTypesOf(HostExtractor.class);
Use the results to create the instances in your factory:
for (Class<? extends HostExtractor> c : extractorTypes) {
HostExtractor he = c.newInstance();
EXTRACTOR_MAPPING.put(he.getHostName(), he);
}
I made up the getHostName method, but it should be trivial to add to the HostExtractor base class.
import android.content.Context;
import android.content.SharedPreferences;
import android.net.Uri;
public class LoadSettings
{
public static void LoadMySettings (Context ctx)
{
SharedPreferences sharedPreferences = ctx.getSharedPreferences("MY_SHARED_PREF", 0);
String strSavedMem1 = sharedPreferences.getString("gSendTo", "");
String strSavedMem2 = sharedPreferences.getString("gInsertInto", "");
String cCalId = sharedPreferences.getString("gCalID", "");
setInsertIntoStr(strSavedMem2);
setSendToStr(strSavedMem1);
}
private static String cSendToStr;
private static String cInsertIntoStr;
private int cCalId;
private Uri cCalendars;
public String getSendToStr()
{
return this.cSendToStr;
}
public static void setSendToStr(String pSendToStr)
{
cSendToStr = pSendToStr;
}
public String getInsertIntoStr()
{
return this.cInsertIntoStr;
}
public static void setInsertIntoStr(String pInsertIntoStr)
{
cInsertIntoStr = pInsertIntoStr;
}
}
from the calling class i have tryed lots the current is.
LoadSettings.LoadMySettings(this);
but when i try to get some data for example.
textSavedMem1.setText(LoadSettings.getSendToStr());
i get a Null error.
LoadMySettings is not a class but a method (so it should start with a lower case, if you follow Oracle/Sun's naming conventions for the Java language).
You access it indeed by calling LoadSettings.loadMySettings(someContext), where someContext is the context to pass around. In your example, we don't know what this refers to, so maybe your error lies there.
Then when you do this: textSavedMem1.setText(LoadSettings.getSendToStr());
You call a non-static method, so that should be either using an instance of LoadSettings or, more likely considering your code, you could change getSendToStr to be:
public static String getSendToStr()
{
return cSendToStr;
}
Though that seems to be rather bad design.
Maybe if you tell us more about what you try to do, we can help more, as as such our answers will just take you one step further.
EDIT: Ok, I just figured out what you are trying to do...
You need to go back and learn basic Java concepts and read on access modifiers, and constructors first, and OO semantics in Java in general.
Change your class to this:
public class LoadSettings
{
public LoadSettings(Context ctx)
{
SharedPreferences sharedPreferences =
ctx.getSharedPreferences("MY_SHARED_PREF", 0);
String strSavedMem1 = sharedPreferences.getString("gSendTo", "");
String strSavedMem2 = sharedPreferences.getString("gInsertInto", "");
String cCalId = sharedPreferences.getString("gCalID", "");
setInsertIntoStr(strSavedMem2);
setSendToStr(strSavedMem1);
}
private String cSendToStr;
private String cInsertIntoStr;
private int cCalId;
private Uri cCalendars;
public String getSendToStr()
{
return cSendToStr;
}
public void setSendToStr(String pSendToStr)
{
cSendToStr = pSendToStr;
}
public String getInsertIntoStr()
{
return cInsertIntoStr;
}
public void setInsertIntoStr(String pInsertIntoStr)
{
cInsertIntoStr = pInsertIntoStr;
}
}
And create a new instance of LoadSettings with:
LoadSettings mySettings = new LoadSettings(someContext);
You can then correctly invoke:
textSavedMem1.setText(mySettings.getSendToStr());
Haylem has the right of it, but I just wanted to add a comment:
There are basically two design patterns in Java for what you're trying to do. One is the static class where all the methods and variables are static and you access them as e.g.
LoadSettings.loadMySettings(this);
string = LoadSettings.getSendToStr()
// etc.
The other pattern is the "singleton" class where you create exactly one instance of the class and you access the instance:
LoadSettings ls = new LoadSettings(this);
ls.loadMySettings();
string = ls.getSendToStr();
Either way is good, but what you're doing is a mish-mash of both methods and it won't work.
For an enum with only one member variable, is there a simpler (and best practices) way to access it without the use of an accessor method? I had alternatively considered using public static final variables in a constants class but the more I read, the more people suggest using enum as the way to encapsulate those values.
To attempt to illustrate what I mean, I've included the following example:
public enum FILE_NAME {
MAIN("MAIN.TXT"),
ATTACHMENT("ATTACHMENT.TXT"),
OTHER("OTHER.HTM");
private String fileName;
FILE(String fileName) {
this.fileName = fileName;
}
public String getfileName() {
return fileName;
}
}
I would then normally access that value like so:
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ZipOutputStream zip = new ZipOutputStream(bos);
// Add file
zip.putNextEntry(new ZipEntry(FILE_NAME.MAIN.getFileName()));
For my particular use case, I'd much prefer to be access the filename with a call like:
...
zip.putNextEntry(new ZipEntry(FILE_NAME.MAIN));
In doing so, the code reduces the length (and almost syntactic redundancy of calling a filename of a file) of the call needed to access the MAIN file name text. While this may not even be feasible or desirable, I'm curious to know if it's worth considering.
Thanks.
To simplify it, and still keep it safe, use a public final String field for the file name:
public enum FileType {
MAIN("MAIN.TXT"),
ATTACHMENT("ATTACHMENT.TXT"),
OTHER("OTHER.HTM");
// Name in all-caps to make it look like the constant it is
public final String FILENAME;
private FileType(String fileName) {
this.FILENAME = fileName;
}
}
To use it:
zip.putNextEntry(new ZipEntry(FileType.MAIN.FILENAME));
Note that change of class name to "FileType" to better adhere to java standards.
It seems like the following would work better
public enum FILE_NAME {
MAIN("MAIN.TXT"),
ATTACHMENT("ATTACHMENT.TXT"),
OTHER("MAIN.TXT");
private String fileName;
FILE(String fileName) {
this.fileName = fileName;
}
public void putEntry ( ZipOUtputStream zip )
{
zip.putNextEntry(new ZipEntry(this.getFileName()));
}
public String getfileName() {
return fileName;
}
}
Then you could use it like:
FILE_NAME.MAIN.putEntry(zip);
You can have aliases
enum Pet
{
cat("meow"),
dog("woof")
String getSound()
}
public static final Pet CAT = Pet.cat;
public static final String MEOW = Pet.cat.getSound();
So I have this method that get executed repeatedly
public static boolean isReady(String dirPath, int numPdfInPrintJob){
File dir = new File(dirPath);
String[] fileList = dir.list(new FilenameFilter(){
public boolean accept(File file, String filename) {
return (filename.toLowerCase().endsWith(".pdf"));
}
});
if(fileList.length >= numPdfInPrintJob) return true;
else return false;
}
This method using anonymous class that will create a new instance of FilenameFilter every time invoked and I invoke this method a lot. So I want to make this anonymous class into a singleton. So my initial thought is to create a new singleton class that look like this
public class PdfFileNameFilter implements FilenameFilter{
private PdfFileNameFilter(){} //non-instantible
//guarantee to only have one instance at all time
public static final PdfFileNameFilter INSTANCE = new PdfFileNameFilter();
public boolean accept(File dir, String name) {
return (name.toLowerCase().endsWith(".pdf"));
}
}
Can I refactor this a bit more. I need to do ZipFileNameFilter as well, and maybe many different file extension filter. Dont want to create a class for each filter. I need to refactor this design a bit more. Maybe interface come into place somewhere here.
If all you wanted to do was reduce memory usage you could have done
private static final FilenameFilter PDF_FILES = new FilenameFilter(){
public boolean accept(File file, String filename) {
return (filename.toLowerCase().endsWith(".pdf"));
}
}
If you want to create a singleton, the simplest way is
public enum PdfFileNameFilter implements FilenameFilter {
INSTANCE;
public boolean accept(File dir, String name) {
return (name.toLowerCase().endsWith(".pdf"));
}
}
It seems simpler to me to just use your existing anonymous class and make one instance that all your method invocations use.
private static final FilenameFilter PDF_FILTER = new FilenameFilter() {
public boolean accept(File file, String filename) {
return (filename.toLowerCase().endsWith(".pdf"));
}
}
public static boolean isReady(String dirPath, int numPdfInPrintJob){
File dir = new File(dirPath);
String[] fileList = dir.list(pdfFilter);
if(fileList.length >= numPdfInPrintJob) return true;
else return false;
}
This is a case where subclassing and making a singleton seems to be a tad overkill: you simply want only one instance to use right here, whereas a singleton is used when there is only one instance you will ever want to use.
You could use an enum for this:
public enum ExtensionFilter implements FilenameFilter {
PDF(".pdf"),
ZIP(".zip");
private final String extension;
private ExtensionFilter(String extension) {
this.extension = extension;
}
#Override
public boolean accept(File dir, String name) {
return (name.toLowerCase().endsWith(extension));
}
}
Now you'll be able to use it like:
dir.list(ExtensionFilter.PDF)
You can also iterate through them if you need:
for ( FilenameFilter fileNameFilter : ExtensionFilter.values() ) {
....
}
You could also use a vararg as constructor argument to allow multiple extensions for the same filter and use the constant name as the default to make it simpler to use:
public enum ExtensionFilter implements FilenameFilter {
PDF,
ZIP(".zip", ".jar", ".war", ".ear");
private final String[] extensions;
private ExtensionFilter(String... extensions) {
if (extensions.length == 0) {
extensions = new String[] {"." + name().toLowerCase()};
}
this.extensions = extensions;
}
#Override
public boolean accept(File dir, String name) {
for (String extension : extensions) {
if (name.toLowerCase().endsWith(extension)) {
return true;
}
}
return false;
}
}
you can step away from the full singleton and use a private field to set up the extension
public class ExtensionFileNameFilter implements FilenameFilter{
private String extension;
private ExtensionFileNameFilter (String extension){this.extension=extension;}
public static final ExtensionFileNameFilter PDFINSTANCE = new ExtensionFileNameFilter (".pdf");
public static final ExtensionFileNameFilter ZIPINSTANCE = new ExtensionFileNameFilter (".zip");
//add instances as you need
public boolean accept(File dir, String name) {
return (name.toLowerCase().endsWith(extension));
}
}
Can I refactor this a bit more.
Yes, yes you can.
Assuming that wasn't the answer you were looking for (you should update yoru question to ask a more specific question), I wouldn't refactor it until you need it; YAGNI.
Once you have more code, like more FilenameFilters, is when you will see the possible refactorings. You will see common code, maybe an interface, stuff like that. Don't try to pre-maturely over-engineeer.
TDD is also the best way to do refactoring safely. If you have tests showing what code you actually need, lots of the extra stuff goes away, if any. And with a comprehensive test suite, you can refactor without hesitation because you know if your changes work or not based on whether your tests continue to pass.
For interest, this alternative accept implementation would run much faster in a benchmark test. It does not create new stateful Objects or carry the other overhead of String.toLowerCase, which is not required for your case.
public boolean accept(File file, String filename) {
int offset = s.length() - 4;
if (offset >= 0) {
if (s.charAt(offset) == '.') {
offset += 1;
if (s.regionMatches(offset, "pdf", 0, 3)) {
return true;
} else if (s.regionMatches(offset, "PDF", 0, 3)) {
return true;
}
}
}
return false;
}
If this was an execution hotspot and you were looking for optimization, something like that would help.