Edit: A follow-up question based on this discussion was published in the following link.
Android: How to manage common codebase in multiple libraries used by the same application
I have two android aar library projects: LibA using ClassA, and LibB using ClassB. Both libs have the same base package. both libs use the same class named BaseClass, currently resides separately within each lib in package name 'common'. BaseClass contains one method named baseMethod.
This creates two libs using a class with the same name and a different implementation.
this is how the classes look like:
ClassA:
package mybasepackage.a;
import mybasepackage.common.BaseClass;
public class ClassA {
BaseClass baseClass;
public ClassA() {
this.baseClass= new BaseClass();
}
public String myPublicMethod(){
return this.baseClass.baseMethod();
}
}
ClassB:
package mybasepackage.b;
import mybasepackage.common.BaseClass;
public class ClassB {
BaseClass baseClass;
public ClassB() {
this.baseClass = new BaseClass();
}
public String myPublicMethod(){
return this.baseClass.baseMethod();
}
}
BaseClass In LibA:
package mybasepackage.common;
public class BaseClass{
public String baseMethod() {
return "Called from ClassA";
}
}
BaseClass in LibB:
package mybasepackage.common;
public class BaseClass{
public String baseMethod() {
return "Called from ClassB";
}
}
When I try to compile both libs in the same app, it throws a duplicated class error: "Program type already present: mybasepackage.common.BaseClass", this happens because the compiler cannot know which BaseClass to compile since it resides within both libs.
My goal is to allow both aar libs to compile successfully within the same app, while providing different implementations for the BaseClass. More formally, LibA and LibB should compile in the same application such as:
Calling new ClassA().baseMethod() will return "Called from ClassA".
Calling new ClassB().baseMethod() will return "Called from ClassB".
Pre condition: I cannot change the base package name in one of the libs because it essentially creates an unwanted duplication of BaseClass.
NOTE: I'm aware this may not be possible via the aar approach. If that is truly the case, I'm willing to consider other deployment architectures as long as I'll be able to compile these libs with the same common class using different implementations, as described in the question.
My goal is to allow both aar libs to compile successfully within the same app, while providing different implementations for the BaseClass
That is not possible, sorry.
I'm aware this may not be possible via the aar approach.
It has nothing to do with AARs. You cannot have two classes with the same fully-qualified class name in the same app, period. It does not matter where those duplicate classes come from.
I'm willing to consider other deployment architectures as long as I'll be able to compile these libs with the same common class using different implementations, as described in the question.
That is not possible, sorry. Again: it does not matter where the duplicate classes come from. You simply cannot have duplicate classes.
Given your precondition you just can't do that in this way. You cannot have 2 different libraries in java with the same package name, which is the main problem that throws your error (and not the name of the classes).
What you can do and maybe if possible is the best way to handle with that is to merge the two libraries into just one and add two subpackages inside and then just import them:
import mybasepackage.common.a_name.BaseClass; // class A
import mybasepackage.common.b_name.BaseClass; // class B
This will prevent the duplication error because they just have the same name but from different packages.
Another idea if this way doesn't fit your expectation is to change the architecture by implementing another abstraction layer in which you define your BaseClass as an abstract method:
package mybasepackage.common;
public class abstract BaseClass{
public String myPublicMethod();
}
and then you just implement the method inside ClassA and ClassB:
public class ClassA implements BaseClass{
public ClassA() {
super();
}
#Override
public String myPublicMethod(){
// logic for A
}
}
NB note that the above implementation of class A is just a stub and it is not supposed to work as it is. Adapt to your need.
In any case by the way you can't have two packages with same classes name.
Just build three artifacts, because two artifacts will always require an exclude on one of the dependencies set. When the two -liba and -libb libraries depend on a third -base, -core or -common library, there are no duplicate classes - and if you want to keep the package name, just make the package name depend on all of them, alike a meta-package:
mybasepackage
|
mybasepackage-liba -> mybasepackage-common
|
mybasepackage-libb -> mybasepackage-common
mybasepackage-common
Related
Let's say that in my own project I use a 3rd party package named com.3rdparty.foo, and in that package there's a class with a package-private field:
package com.3rdparty.foo;
public class Bar {
public void someMethod () { ... }
int ppField;
}
Now I realize that the package-private fields aren't supposed to be accessed outside of that package. But, out of curiosity, is it technically possible to write my own class and attribute it to that 3rd party package, for example, to circumvent the package-private field protection:
// MyOwnClass.java
package com.3rdparty.foo;
import com.3rdparty.foo.Bar;
public class MyOwnClass {
public int hackPPField (Bar b) { return b.ppField; }
}
I expect this to be technically impossible. But, if it's indeed so, then I would like to know the exact mechanism of package sealing which prevents you from arbitrarily extending the 3rd party packages.
Yes, perfectly possible in the general case. Your class and other classes in the package have to be loaded by the same classloader, if I'm not mistaken, and you can't put classes in certain packages (java.lang, for example; in fact, I think it's all the java. packages), but in the general case nothing distinguishes your class from other classes in that com.3rdparty.foo.
I have two questions:
1) Is there a way to extend of class A from external file ? how?
2) I am building one class of my custom methods ( to use globally, in all my projects). Here is phseudo-code:
package MyFunctions;
import Twitter.profile;
public class MyFuncs{
public String externalProfile1() { return Twitter.profile.TwitterUrl(); }
}
I want Is there a way to include that file in all my projects, and avoid IDE errors, as I should be able to use any when one of the above functions in my projects... The problem is that the Twitter.Profile classes are not included in all my projects, and whenever happens so - it see error in IDE("cannot find symbol method")...
how to solve the problem?
Question 2:
Just make sure all of your functions in your library are written statically:
public class MyFuncs{
public static String externalProfile1(String link) { return TwittUrl() + "/profile"; }
public static String externalProfile2(String link) { return YahooUrl() + "/profile"; }
}
And then import that class in your project files that you'll be using your library in. Then you can easily call the functions in your library. Alternatively, you can avoid importing the library in every other file and instead call the functions in a static way:
MyFuncs.externalProfile1("link");
As for TwittUrl(), if it doesn't require to be in a separate Class, then refactor it and put it in MyFuncs class; otherwise you can make TwittUrl() and YahooUrl() methods static in their own class.
I have a rather unique situation where I know my Java web app will always be packaged with 1-and-only-1 concrete subclass of an AbstractWidget:
public abstract class AbstractWidget {
// ...
}
public class SimpleWidget extends AbstractWidget {
// ...
}
public class ComplexWidget extends AbstractWidget {
// ...
}
public class CrazyComplexWidget extends AbstractWidget {
// ...
}
// ...etc.
Again, I know at runtime that my WAR/WEB-INF/classes directory will always have 1-and-only-1 AbstractWidget impl packaged in it (no more, no less), be it ComplexWidget.class, SimpleWidget.class, etc.
I'm trying to construct code (that would actually run when the WAR starts up from inside its ServletContextListener impl) that would be able to scan the runtime classpath and obtain an instance (using public no-arg constructor) of the AbstractWidget.
Thus, if my WAR has:
myWar/
WEB-INF/
lib/
classes/
com/
myorg/
App (implements ServletContextListener)
... lots of other classes and packages
some/
arbitrary/
package/
SimpleWidget
Then, from inside App#contextInitialized(ServletContextEvent) I need code that will find SimpleWidget.class on the classpath and give me an instance of it:
public class App implements ServletContextListener {
#Override
public void contextInitialized(ServletContextEvent event) {
// Scan classpath for the lone AbstractWidget impl somehow.
???
// Use public, no-arg ctor to instantiate the impl.
AbstractWidget widget = ???
// Now do stuff with widget...
}
}
I know you can use reflection methods like Class.isAssignableFrom(), but not sure if that is the correct way to go, and even if it is, how to use it for my given use case. Any ideas? Thanks in advance!
You might want to check out the reflections api. It has utilities for finding the subclass(es) of a given class. You can do something like this with it:
Reflections reflections = new Reflections();
Set<Class<? extends AbstractWidget>> subclasses;
subclasses = reflections.getSubTypesOf(AbstractWidget.class);
This will get you a Set containing all the subclasses of the AbstractWidget class on the classpath. http://code.google.com/p/reflections/
I know it is very old question but ClassGraph library can help you very easily get all classes in the classpath extending a given class, basically all subclasses of a given class.
Gradle dependency
compile group: 'io.github.classgraph', name: 'classgraph', version: '4.8.46'
This how you can easily you can scan classpath
ScanResult scanResult = new ClassGraph()
.whitelistPackages("com.myorg") //whatever package you want to scan
.verbose()
.enableAllInfo()
.scan();
System.out.println("Classes which extending '" + AbstractWidget.class.getSimpleName() + "' class");
ClassInfoList classInfoList = scanResult.getSubclasses(AbstractWidget.class.getName());
for (ClassInfo classInfo : classInfoList) {
System.out.println("\t" + classInfo.getName());
}
Output assuming you have SimpleWidget class in the classpath
Classes which extending 'AbstractWidget' class
com.myorg.some.arbitrary.package.SimpleWidget
I hope it helps. ClassGraph is really powerful library and can do much more this simple need. Its explain really well in this article https://readtorakesh.com/java-classpath-scanning-using-classgraph/
I made class implementation that packaging the needed classes and send it to execute on server.
I did a method using org.reflections that allows me to load all needed classes to this job. I generate a jar file with this classes and it will be executed on server by a web service. This is already done.
But, I have a situation where occurs a problem that I cannot solve until now.
Ex:
package com.marciob.applications.report.generator;
import com.marciob.applications.onleague.model.Team;
class MyJob implements Job {
public void execute(Team team) {
...
}
}
package com.marciob.applications.onleague.model;
class Team {
private List<Player> players;
// getters and setters
}
When I generate the jar file, there is a MyJob and Team class, but the class Player that is needed by Team class is not found as a dependency because is not found in import statement of Team class.
Anyone knows a way to do this? Find all needed classes, including that was is not indicated in import statement because is in the same package?
Just as sdoca said, it is a mystery how you package your classes.
Since the Player class is in the same package as the Team class, the only way for them not to be packaged together could be if they are in different source folders, i.e. like ${project}/src/main/java and ${project}/src/test/java.
The only way that I can think of (at this moment) to discover the classes which the target class depends on is by analyzing the bytecode, just like the Class Dependency Analyzer tool does it.
how can I use resources from other maven modules? My goal is to provide a AbstractImportClass as well as the to be imported files in a specific maven module. And use this module within other modules extending this class.
Let's say ModuleA contains src/main/java/MyAbstractImportClass.java, and src/main/resources/MyImport.csv
I now want to use the abstract import class in ModuleB. Or rather, I will extend it, use the abstract-fileimport, and a few custom functions.
Then ModuleC also uses the abstracts' import and some custom functions.
The problem is: the import in abstract class goes with reader and InputStream. When I execute just ModuleA everything is fine.
But when I tried to include the module via maven pom, and then extend the module to call the import, then I get NullPointerException at the line where the reader is used.
So obvious I cannot use foreign module resources this way.
But how could I instead make use of this?
Update:
Module A:
src/main/java/path/to/MyClassA.java
src/main/resources/path/to/test.txt
abstract class MyClassA {
public static String TESTFILE = test.txt;
List<String> doImport(String filename) {
InputStream fileStream = resourceClass.getResourceAsStream(filename);
//some precessing
return list;
}
}
Module B:
src/main/java/path/to/MyClassB.java
class MyClassB implements MyClassA {
List<String> list = doImport(TESTFILE);
}
If I put MyClassB in same dir as A, then everything works fine.
If I build B in a own module I get NullPointer for InputStream, what means the file is not found.
I don't think your problem is related to Maven at all. Class.getResourceAsStream() resolves relative paths as relative to the class object that you call it on. Therefore, if you use that method in an abstract class, every subclass of it could be looking for the resource in a different place.
For example, given three classes:
Super:
package com.foo;
public class Super {
{ System.out.println(getClass().getResourceAsStream("test.properties")); }
}
Sub1, a subclass of Super:
package com.foo.bar;
import com.foo.Super;
public class Sub1 extends Super {}
Sub2, another subclass:
package com.foo.bar.baz;
import com.foo.Super;
public class Sub2 extends Super {}
If you create a Super, it'll look for the classpath resource "/com/foo/test.properties" because that's how the path "test.properties" resolves relative to the class com.foo.Super. If you create a Sub1, it'll look instead in "/com/foo/bar/test.properties", and for a Sub2 instance, it'll look in "/com/foo/bar/baz/test.properties".
You might want to use an absolute path to the resource instead of a relative one, or else have the subclasses specify paths relative to themselves. It depends on your design and what kind of abstraction you're trying to achieve.
It's not exactly clear what your code does. Could you provide sample of how you're reading resource? If you do it properly - by getting InputStream from resource file in classpath there should be no problem. You can start by checking that ModuleA.jar has your resource file inside.
You should check:
Module B depend on Module A in pom.xml
The passed in 'filename' parameter starts with a '/', that is to say, the 'filename' parameter is '/path/to/test.txt' other than 'path/to/test.txt'
You program should work if these two conditions is satisfield.