1

I have this code and this error constantly is appearing. I have only one excel , but nothing seems to work, I already tried a lot of option that I found surfing on internet, but nothing seems to work according of what I want to do.

I use different case to make easier the logical of my business and I am not going to change that, so I am not sure how to do solve this issue.

private static final String nombreArchivo = "casoPrueba.xlsx";
private static final String rutaArchivo = "src\\test\\resources\\data\\" + nombreArchivo;




 public static XSSFSheet SacaHojaSegunTipo(String tipo) throws IOException {
        if (workbook == null) {
            try (FileInputStream fis = new FileInputStream(new File(rutaArchivo))) {
                workbook = new XSSFWorkbook(fis);
            }
        }
        XSSFSheet spreadsheet = null;
        switch (tipo) {
            case "Candidatos Minorista":
                spreadsheet = workbook.getSheetAt(1);
                break;
            case "Conversion Candidatos":
                spreadsheet = workbook.getSheetAt(2);
                break;
            case "Cuentas":
                spreadsheet = workbook.getSheetAt(3);
                break;
            case "Detalle Cuenta":
                spreadsheet = workbook.getSheetAt(4);
                break;
            case "Historial de Cuentas":
                spreadsheet = workbook.getSheetAt(5);
                break;
            case "Cuentas Financieras":
                spreadsheet = workbook.getSheetAt(6);
                break;
            case "AR Estado Automático":
                spreadsheet = workbook.getSheetAt(7);
                break;
            case "Oportunidades":
                spreadsheet = workbook.getSheetAt(8);
                break;
            default:
                spreadsheet = workbook.getSheetAt(0);
                break;
        }
        return spreadsheet;
    }

I know this is not a efficient method.Hope anyone can help me with this.

  • What's the file's size? – rodrigoap Jul 09 '19 at 22:48
  • @rodrigoap 260 kb –  Jul 09 '19 at 22:49
  • Are you loading the file multiple times? It seems like the SacaHojaSegunTipo method is with no reason loading the file every time a sheet is requested. It doesn't seem right. – rodrigoap Jul 09 '19 at 22:53
  • @rodrigoap I am using java with Maven for automation on the website, I use this for any kind of test. That's why I use the case, about your reply, I don't think that this is with no reason because it depends on the test if it's sheet(1) or sheet(2) –  Jul 09 '19 at 22:58
  • 1
    Then it might be Maven: https://stackoverflow.com/questions/4066424/java-lang-outofmemoryerror-java-heap-space-in-maven – rodrigoap Jul 09 '19 at 23:03
  • 2
    Two big issues with your code : First, as @rodrigoap says, you are loading the file each time a sheet is requested. Sorry, I don't care how your tests want it, this is your production code. Read the file ONCE (maybe into a static variable since this is a static method), and return sheets from that one copy. Second, you do not close your FileInputStream (which would eat resources - see https://stackoverflow.com/questions/22889075/do-unclosed-streams-cause-memory-leaks-in-java ) – racraman Jul 09 '19 at 23:17
  • @recraman , you are right and also what rodrigoap says is right. But, I don't know how I could make this , I only implemented that from what I read and was working. I would be really appreciate if you help me to implement what you say. how I would make a static variable from my static method?. –  Jul 09 '19 at 23:35
  • @racraman hi could you help me do that? I still have the same problem. –  Aug 07 '19 at 23:23
  • @rodrigoap rodrigo could you help me make a statis variable? I still have the same problem and I don't have much experience to improve this code. –  Aug 07 '19 at 23:29
  • You put it in the class body and then put the word "static" before it. – ifly6 Aug 07 '19 at 23:34
  • @ifly6 but how that would make that the file will open once and not multiple time as now?. Sorry if this is a silly question, I don't have so much experience programming. –  Aug 07 '19 at 23:35
  • You have to change the code to initialise it once. In pseudo-code if sheet == null load, then continue doing what you're doing – ifly6 Aug 07 '19 at 23:37
  • @ifly6 really thank you for your comment, but doesn't help me a lot, I still don't understand how this would open the file once when my tests need different kind of information depending of the sheet. Also, how I would put that in pseudo-code? because the sheet will change depending the test that is running. –  Aug 07 '19 at 23:40

2 Answers2

0

Something like this (I tried to change your code as little as possible, so it's not perfect)

private static final String nombreArchivo = "casoPrueba.xlsx";
private static final String rutaArchivo = "src\\test\\resources\\data\\" + nombreArchivo;

private static XSSFWorkbook workbook = null;

public static XSSFSheet SacaHojaSegunTipo(String tipo) throws IOException {
    if (workbook == null) {
        try (FileInputStream fis = new FileInputStream(new File(rutaArchivo))) {
            workbook = new XSSFWorkbook(fis);
        }
    }
    XSSFSheet spreadsheet = null;
    switch (tipo) {
    case "Candidatos Minorista":
        spreadsheet  = workbook.getSheetAt(1);          
        break;
    case "Conversion Candidatos":
        spreadsheet  = workbook.getSheetAt(2);          
        break;
    case "Cuentas":
        spreadsheet  = workbook.getSheetAt(3);          
        break;
    case "Detalle Cuenta":
        spreadsheet  = workbook.getSheetAt(4);          
        break;
    case "Historial de Cuentas":
        spreadsheet  = workbook.getSheetAt(5);          
        break;
    case "Navegar Cuentas":
        spreadsheet  = workbook.getSheetAt(6);          
        break;
    case "Validar Número Operación":
        spreadsheet  = workbook.getSheetAt(7);          
        break;
    case "Validar Tipos de Productos":
        spreadsheet  = workbook.getSheetAt(8);          
        break;
    case "Validar Referencia y Cód. Auto.":
        spreadsheet  = workbook.getSheetAt(9);          
        break;
    default:
        spreadsheet = workbook.getSheetAt(0);
    }
    return spreadsheet;
}
rodrigoap
  • 7,405
  • 35
  • 46
  • really thank for your time in trying to improve my code. I tried this , but only works in the first time that I need to get a value, when I need to get another value from another line, appears an error and console shows java.lang.NullPointerException –  Aug 08 '19 at 20:06
0

First, a quick aside : it's worth noting the following from
https://poi.apache.org/apidocs/dev/org/apache/poi/xssf/usermodel/XSSFWorkbook.html#XSSFWorkbook-java.io.InputStream-

Using an InputStream requires more memory than using a File, so if a File is available then you should instead do something like

   OPCPackage pkg = OPCPackage.open(path);
   XSSFWorkbook wb = new XSSFWorkbook(pkg);
   // work with the wb object
   ......
   pkg.close(); // gracefully closes the underlying zip file

(although doing wb.close() also closes the files and streams).

Now, your core issue is that you need to release resources after the sheet or workbook are no longer required, but at present you cannot do so since these are hidden local inside the method.

So you need to give your caller access to close them when it's done. It's a matter of preference, but personally I would prefer encapsulating the spreadsheet into it's own class - a spreadsheet IS a clearly defined object in its own right, after all ! As such, this would necessitate a change from static, so something like :

public class RutaArchivo implements AutoCloseable {
    private static final String nombreArchivo = "casoPrueba.xlsx";
    private static final String rutaArchivo = "src\\test\\resources\\data\\" + nombreArchivo;

    public static final String CANDIDATOS_MINORISTA = "Candidatos Minorista";
    public static final String CONVERSION_CANDIDATOS = "Conversion Candidatos"
    public static final String CUENTAS = "Cuentas";

    private XSSFWorkbook workbook;

    public RutaArchivo() throws InvalidFormatException, IOException {
        workbook = new XSSFWorkbook(new File(rutaArchivo));
    }

    @Override
    public void close() throws Exception {
        if (workbook != null) {
            workbook.close();
            workbook = null;
        }
    }

    public XSSFSheet sacaHojaSegunTipo(String tipo) {
         if (workbook == null) {
             throw new IllegalStateException("It's closed");
         }
         XSSFSheet spreadsheet = workbook.getSheetAt(0);
         if (tipo .equals(CANDIDATOS_MINORISTA)) {
             spreadsheet  = workbook.getSheetAt(1);
         }else if(tipo.equals(CONVERSION_CANDIDATOS)){
             spreadsheet  = workbook.getSheetAt(2);
         }else if(tipo.equals(CUENTAS)){
              spreadsheet  = workbook.getSheetAt(3);
         // etc, etc
         }

         return spreadsheet;
    }
}

A couple of things to note :

  • If we want the caller to close the file, then we should explictly make them take some action to open it as well, otherwise it's too easy for it to be left hanging. In the example above, this is implicit in creating the object - just like the standard Java types like FileInputStream, etc.

  • Making RutaArchivo AutoCloseable means that it can used in try-with-resources, so closed automatically - eg :

    try (RutaArchivo rutaArchivo = new RutaArchivo()) {
        XSSFSheet cuentas = rutaArchivo.getSheet(RutaArchivo.CUENTAS);
    }
    
  • Using constants for the names of the sheets reduces bugs (eg, no typos when the method is called)

  • As this is it's own class rather than static methods, it's easier to substitute or mock when writing unit tests.

Anyhow, a few thoughts - hope they help.

racraman
  • 4,988
  • 1
  • 16
  • 16
  • Hi @racraman your code really help me.Really thank you for your time. I have few questions. When I implement the method close() appears in intellij "return type required" and in override "method doesn't override from its superclass,'not applicable to constructor' . Also, in public rutaArchivo() appears return type required. I am trying to implement your code in my project. –  Aug 08 '19 at 14:33
  • Apologies, I was away from my main computer, so I typed it on the go without my IDE, so there were a couple of mistakes. The `close()` should be `public void close() throws Exception`, which also fixes the override issue. A couple of others as well (constructor should `throw Exception`, and the constants of course should be String, and getSheet returns XSSFSheet.). I've fixed those in the code, but the main thing is in understanding the principles and how it all works, so glad it's helping :) – racraman Aug 08 '19 at 21:04
  • thanks!. your code is really helping me, I have a question. I already implemented the method close, but I don't know how to use it.I don't know how to make this autoclosable. I let my method like this : public static XSSFSheet SacaHojaSegunTipo(String tipo) throws IOException { if (workbook == null) { try (FileInputStream fis = new FileInputStream(new File(rutaArchivo))) { workbook = new XSSFWorkbook(fis); } } –  Aug 12 '19 at 15:14
  • @recraman, I update the code, also implement the class as autoclosable, just don't know how to make the workbook autoclosable. Help me, please. –  Aug 12 '19 at 15:30
  • But the code in your comment is still from your question; you're not using my code. For example, why are you still using `static`? IMHO there are only a few cases where `static` methods are acceptable, and this is definitely not one of them (I like @TellMeHow's answer in https://stackoverflow.com/questions/2671496/java-when-to-use-static-methods ). Also you are still opening the workbook in the getSheet (ok, `SacaHojaSegunTipo`) method, which rules out being able to use try-with-resources. So try with my code, yes? :) (ps Java naming convention is method names start with lower case). – racraman Aug 14 '19 at 06:26
  • I already tried as you say. I have few question. 1. Won't there be a problem if I put RutaArchivo twice?, the main clase is with the name where the workbook is open. Also I don't know how this method will be open public RutaArchivo() throws InvalidFormatException, IOException { workbook = new XSSFWorkbook(new File(rutaArchivo)); } because I don't know who call it. Thanks racraman, you are awesome! –  Aug 14 '19 at 15:42
  • I ask because with this is showing java.lang.NullPointerException, I am not sure what I am doing wrong. –  Aug 14 '19 at 16:07
  • A method with the same name as the class and without a return type is a constructor; it is called implicitly when you create a new instance of the class (so again keep `static` only for small simple stand-alone utilities; generally everything should be in created objects). Since we are using `Autocloseable`, the natural place to create the object is in "try with resources", as I gave in the answer `try (RutaArchivo rutaArchivo = new RutaArchivo()) {`, which will implicitly call that method. Glad to help :) – racraman Aug 14 '19 at 23:43