diff --git a/make/modules/jdk.jpackage/Java.gmk b/make/modules/jdk.jpackage/Java.gmk index 9d31e5417e905..d60e9ac281488 100644 --- a/make/modules/jdk.jpackage/Java.gmk +++ b/make/modules/jdk.jpackage/Java.gmk @@ -27,6 +27,6 @@ DISABLED_WARNINGS_java += dangling-doc-comments COPY += .gif .png .txt .spec .script .prerm .preinst \ .postrm .postinst .list .sh .desktop .copyright .control .plist .template \ - .icns .scpt .wxs .wxl .wxi .ico .bmp .tiff .service + .icns .scpt .wxs .wxl .wxi .ico .bmp .tiff .service .xsl CLEAN += .properties diff --git a/src/hotspot/cpu/aarch64/aarch64.ad b/src/hotspot/cpu/aarch64/aarch64.ad index 190bfdd1f6767..2f2bee6e22f0f 100644 --- a/src/hotspot/cpu/aarch64/aarch64.ad +++ b/src/hotspot/cpu/aarch64/aarch64.ad @@ -5628,24 +5628,24 @@ operand cmpOpLtGe() // used for certain unsigned integral comparisons which can be // converted to cbxx or tbxx instructions -operand cmpOpUEqNeLtGe() +operand cmpOpUEqNeLeGt() %{ match(Bool); op_cost(0); - predicate(n->as_Bool()->_test._test == BoolTest::eq - || n->as_Bool()->_test._test == BoolTest::ne - || n->as_Bool()->_test._test == BoolTest::lt - || n->as_Bool()->_test._test == BoolTest::ge); + predicate(n->as_Bool()->_test._test == BoolTest::eq || + n->as_Bool()->_test._test == BoolTest::ne || + n->as_Bool()->_test._test == BoolTest::le || + n->as_Bool()->_test._test == BoolTest::gt); format %{ "" %} interface(COND_INTER) %{ equal(0x0, "eq"); not_equal(0x1, "ne"); - less(0xb, "lt"); - greater_equal(0xa, "ge"); - less_equal(0xd, "le"); - greater(0xc, "gt"); + less(0x3, "lo"); + greater_equal(0x2, "hs"); + less_equal(0x9, "ls"); + greater(0x8, "hi"); overflow(0x6, "vs"); no_overflow(0x7, "vc"); %} @@ -15687,7 +15687,7 @@ instruct cmpP_narrowOop_imm0_branch(cmpOpEqNe cmp, iRegN oop, immP0 zero, label ins_pipe(pipe_cmp_branch); %} -instruct cmpUI_imm0_branch(cmpOpUEqNeLtGe cmp, iRegIorL2I op1, immI0 op2, label labl, rFlagsRegU cr) %{ +instruct cmpUI_imm0_branch(cmpOpUEqNeLeGt cmp, iRegIorL2I op1, immI0 op2, label labl) %{ match(If cmp (CmpU op1 op2)); effect(USE labl); @@ -15696,15 +15696,17 @@ instruct cmpUI_imm0_branch(cmpOpUEqNeLtGe cmp, iRegIorL2I op1, immI0 op2, label ins_encode %{ Label* L = $labl$$label; Assembler::Condition cond = (Assembler::Condition)$cmp$$cmpcode; - if (cond == Assembler::EQ || cond == Assembler::LS) + if (cond == Assembler::EQ || cond == Assembler::LS) { __ cbzw($op1$$Register, *L); - else + } else { + assert(cond == Assembler::NE || cond == Assembler::HI, "unexpected condition"); __ cbnzw($op1$$Register, *L); + } %} ins_pipe(pipe_cmp_branch); %} -instruct cmpUL_imm0_branch(cmpOpUEqNeLtGe cmp, iRegL op1, immL0 op2, label labl, rFlagsRegU cr) %{ +instruct cmpUL_imm0_branch(cmpOpUEqNeLeGt cmp, iRegL op1, immL0 op2, label labl) %{ match(If cmp (CmpUL op1 op2)); effect(USE labl); @@ -15713,10 +15715,12 @@ instruct cmpUL_imm0_branch(cmpOpUEqNeLtGe cmp, iRegL op1, immL0 op2, label labl, ins_encode %{ Label* L = $labl$$label; Assembler::Condition cond = (Assembler::Condition)$cmp$$cmpcode; - if (cond == Assembler::EQ || cond == Assembler::LS) + if (cond == Assembler::EQ || cond == Assembler::LS) { __ cbz($op1$$Register, *L); - else + } else { + assert(cond == Assembler::NE || cond == Assembler::HI, "unexpected condition"); __ cbnz($op1$$Register, *L); + } %} ins_pipe(pipe_cmp_branch); %} diff --git a/src/hotspot/cpu/s390/assembler_s390.hpp b/src/hotspot/cpu/s390/assembler_s390.hpp index f472af134a358..5820095828985 100644 --- a/src/hotspot/cpu/s390/assembler_s390.hpp +++ b/src/hotspot/cpu/s390/assembler_s390.hpp @@ -1610,6 +1610,9 @@ class Assembler : public AbstractAssembler { static int inv_simm32(long x) { return (inv_s_field(x, 31, 0)); } // 6-byte instructions only static int inv_uimm12(long x) { return (inv_u_field(x, 11, 0)); } // 4-byte instructions only + // NOTE: PLEASE DON'T USE IT NAKED UNTIL WE DROP SUPPORT FOR MACHINES OLDER THAN Z15!!!! + inline void z_popcnt(Register r1, Register r2, int64_t m3); // population count + private: // Encode u_field from long value. @@ -3106,7 +3109,6 @@ class Assembler : public AbstractAssembler { // Ppopulation count intrinsics. inline void z_flogr(Register r1, Register r2); // find leftmost one - inline void z_popcnt(Register r1, Register r2); // population count inline void z_ahhhr(Register r1, Register r2, Register r3); // ADD halfword high high inline void z_ahhlr(Register r1, Register r2, Register r3); // ADD halfword high low diff --git a/src/hotspot/cpu/s390/assembler_s390.inline.hpp b/src/hotspot/cpu/s390/assembler_s390.inline.hpp index 51b2cbe0a3e8a..2649d0f7a3496 100644 --- a/src/hotspot/cpu/s390/assembler_s390.inline.hpp +++ b/src/hotspot/cpu/s390/assembler_s390.inline.hpp @@ -748,7 +748,7 @@ inline void Assembler::z_brxhg(Register r1, Register r3, Label& L) {z_brxhg(r1, inline void Assembler::z_brxlg(Register r1, Register r3, Label& L) {z_brxlg(r1, r3, target(L)); } inline void Assembler::z_flogr( Register r1, Register r2) { emit_32( FLOGR_ZOPC | reg(r1, 24, 32) | reg(r2, 28, 32)); } -inline void Assembler::z_popcnt(Register r1, Register r2) { emit_32( POPCNT_ZOPC | reg(r1, 24, 32) | reg(r2, 28, 32)); } +inline void Assembler::z_popcnt(Register r1, Register r2, int64_t m3) { emit_32( POPCNT_ZOPC | reg(r1, 24, 32) | reg(r2, 28, 32) | uimm4(m3, 16, 32)); } inline void Assembler::z_ahhhr( Register r1, Register r2, Register r3) { emit_32( AHHHR_ZOPC | reg(r3, 16, 32) | reg(r1, 24, 32) | reg(r2, 28, 32)); } inline void Assembler::z_ahhlr( Register r1, Register r2, Register r3) { emit_32( AHHLR_ZOPC | reg(r3, 16, 32) | reg(r1, 24, 32) | reg(r2, 28, 32)); } diff --git a/src/hotspot/cpu/s390/macroAssembler_s390.cpp b/src/hotspot/cpu/s390/macroAssembler_s390.cpp index ef5216a12ba13..275f4a8d832e3 100644 --- a/src/hotspot/cpu/s390/macroAssembler_s390.cpp +++ b/src/hotspot/cpu/s390/macroAssembler_s390.cpp @@ -5803,3 +5803,87 @@ void MacroAssembler::lightweight_unlock(Register obj, Register hdr, Register tmp z_alsi(in_bytes(JavaThread::lock_stack_top_offset()), Z_thread, -oopSize); // pop object z_cr(tmp, tmp); // set CC to EQ } + +void MacroAssembler::pop_count_int(Register r_dst, Register r_src, Register r_tmp) { + BLOCK_COMMENT("pop_count_int {"); + + assert(r_tmp != noreg, "temp register required for pop_count_int, as code may run on machine older than z15"); + assert_different_registers(r_dst, r_tmp); // if r_src is same as r_tmp, it should be fine + + if (VM_Version::has_MiscInstrExt3()) { + pop_count_int_with_ext3(r_dst, r_src); + } else { + pop_count_int_without_ext3(r_dst, r_src, r_tmp); + } + + BLOCK_COMMENT("} pop_count_int"); +} + +void MacroAssembler::pop_count_long(Register r_dst, Register r_src, Register r_tmp) { + BLOCK_COMMENT("pop_count_long {"); + + assert(r_tmp != noreg, "temp register required for pop_count_long, as code may run on machine older than z15"); + assert_different_registers(r_dst, r_tmp); // if r_src is same as r_tmp, it should be fine + + if (VM_Version::has_MiscInstrExt3()) { + pop_count_long_with_ext3(r_dst, r_src); + } else { + pop_count_long_without_ext3(r_dst, r_src, r_tmp); + } + + BLOCK_COMMENT("} pop_count_long"); +} + +void MacroAssembler::pop_count_int_without_ext3(Register r_dst, Register r_src, Register r_tmp) { + BLOCK_COMMENT("pop_count_int_without_ext3 {"); + + assert(r_tmp != noreg, "temp register required for popcnt, for machines < z15"); + assert_different_registers(r_dst, r_tmp); // if r_src is same as r_tmp, it should be fine + + z_popcnt(r_dst, r_src, 0); + z_srlg(r_tmp, r_dst, 16); + z_alr(r_dst, r_tmp); + z_srlg(r_tmp, r_dst, 8); + z_alr(r_dst, r_tmp); + z_llgcr(r_dst, r_dst); + + BLOCK_COMMENT("} pop_count_int_without_ext3"); +} + +void MacroAssembler::pop_count_long_without_ext3(Register r_dst, Register r_src, Register r_tmp) { + BLOCK_COMMENT("pop_count_long_without_ext3 {"); + + assert(r_tmp != noreg, "temp register required for popcnt, for machines < z15"); + assert_different_registers(r_dst, r_tmp); // if r_src is same as r_tmp, it should be fine + + z_popcnt(r_dst, r_src, 0); + z_ahhlr(r_dst, r_dst, r_dst); + z_sllg(r_tmp, r_dst, 16); + z_algr(r_dst, r_tmp); + z_sllg(r_tmp, r_dst, 8); + z_algr(r_dst, r_tmp); + z_srlg(r_dst, r_dst, 56); + + BLOCK_COMMENT("} pop_count_long_without_ext3"); +} + +void MacroAssembler::pop_count_long_with_ext3(Register r_dst, Register r_src) { + BLOCK_COMMENT("pop_count_long_with_ext3 {"); + + guarantee(VM_Version::has_MiscInstrExt3(), + "this hardware doesn't support miscellaneous-instruction-extensions facility 3, still pop_count_long_with_ext3 is used"); + z_popcnt(r_dst, r_src, 8); + + BLOCK_COMMENT("} pop_count_long_with_ext3"); +} + +void MacroAssembler::pop_count_int_with_ext3(Register r_dst, Register r_src) { + BLOCK_COMMENT("pop_count_int_with_ext3 {"); + + guarantee(VM_Version::has_MiscInstrExt3(), + "this hardware doesn't support miscellaneous-instruction-extensions facility 3, still pop_count_long_with_ext3 is used"); + z_llgfr(r_dst, r_src); + z_popcnt(r_dst, r_dst, 8); + + BLOCK_COMMENT("} pop_count_int_with_ext3"); +} diff --git a/src/hotspot/cpu/s390/macroAssembler_s390.hpp b/src/hotspot/cpu/s390/macroAssembler_s390.hpp index 924583abdf563..9f45542dd65cf 100644 --- a/src/hotspot/cpu/s390/macroAssembler_s390.hpp +++ b/src/hotspot/cpu/s390/macroAssembler_s390.hpp @@ -1,6 +1,7 @@ /* - * Copyright (c) 2016, 2023, Oracle and/or its affiliates. All rights reserved. - * Copyright (c) 2016, 2023 SAP SE. All rights reserved. + * Copyright (c) 2016, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 2024 SAP SE. All rights reserved. + * Copyright (c) 2024 IBM Corporation. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -1021,6 +1022,19 @@ class MacroAssembler: public Assembler { Register z, Register tmp1, Register tmp2, Register tmp3, Register tmp4, Register tmp5); + + // These generate optimized code for all supported s390 implementations, and are preferred for most uses. + void pop_count_int(Register dst, Register src, Register tmp); + void pop_count_long(Register dst, Register src, Register tmp); + + // For legacy (pre-z15) use, but will work on all supported s390 implementations. + void pop_count_int_without_ext3(Register dst, Register src, Register tmp); + void pop_count_long_without_ext3(Register dst, Register src, Register tmp); + + // Only for use on z15 or later s390 implementations. + void pop_count_int_with_ext3(Register dst, Register src); + void pop_count_long_with_ext3(Register dst, Register src); + }; /** diff --git a/src/hotspot/cpu/s390/s390.ad b/src/hotspot/cpu/s390/s390.ad index 28cac16864dee..56cf494d27e0a 100644 --- a/src/hotspot/cpu/s390/s390.ad +++ b/src/hotspot/cpu/s390/s390.ad @@ -10675,10 +10675,49 @@ instruct countTrailingZerosL(revenRegI dst, iRegL src, roddRegL tmp, flagsReg cr // bit count +instruct popCountI_Ext3(iRegI dst, iRegI src, flagsReg cr) %{ + match(Set dst (PopCountI src)); + effect(TEMP_DEF dst, KILL cr); + predicate(UsePopCountInstruction && + VM_Version::has_PopCount() && + VM_Version::has_MiscInstrExt3()); + ins_cost(DEFAULT_COST); + size(8); // popcnt + llgfr + format %{ "POPCNT $dst,$src\t # pop count int" %} + ins_encode %{ + Register Rdst = $dst$$Register; + Register Rsrc = $src$$Register; + + __ pop_count_int_with_ext3(Rdst, Rsrc); + + %} + ins_pipe(pipe_class_dummy); +%} + +instruct popCountL_Ext3(iRegI dst, iRegL src, flagsReg cr) %{ + match(Set dst (PopCountL src)); + effect(TEMP_DEF dst, KILL cr); + predicate(UsePopCountInstruction && + VM_Version::has_PopCount() && + VM_Version::has_MiscInstrExt3()); + ins_cost(DEFAULT_COST); + size(4); // popcnt + format %{ "POPCNT $dst,$src\t # pop count long" %} + ins_encode %{ + Register Rdst = $dst$$Register; + Register Rsrc = $src$$Register; + + __ pop_count_long_with_ext3(Rdst, Rsrc); + %} + ins_pipe(pipe_class_dummy); +%} + instruct popCountI(iRegI dst, iRegI src, iRegI tmp, flagsReg cr) %{ match(Set dst (PopCountI src)); effect(TEMP_DEF dst, TEMP tmp, KILL cr); - predicate(UsePopCountInstruction && VM_Version::has_PopCount()); + predicate(UsePopCountInstruction && + VM_Version::has_PopCount() && + (!VM_Version::has_MiscInstrExt3())); ins_cost(DEFAULT_COST); size(24); format %{ "POPCNT $dst,$src\t # pop count int" %} @@ -10687,17 +10726,8 @@ instruct popCountI(iRegI dst, iRegI src, iRegI tmp, flagsReg cr) %{ Register Rsrc = $src$$Register; Register Rtmp = $tmp$$Register; - // Prefer compile-time assertion over run-time SIGILL. - assert(VM_Version::has_PopCount(), "bad predicate for countLeadingZerosI"); - assert_different_registers(Rdst, Rtmp); + __ pop_count_int_without_ext3(Rdst, Rsrc, Rtmp); - // Version 2: shows 10%(z196) improvement over original. - __ z_popcnt(Rdst, Rsrc); - __ z_srlg(Rtmp, Rdst, 16); // calc byte4+byte6 and byte5+byte7 - __ z_alr(Rdst, Rtmp); // into byte6 and byte7 - __ z_srlg(Rtmp, Rdst, 8); // calc (byte4+byte6) + (byte5+byte7) - __ z_alr(Rdst, Rtmp); // into byte7 - __ z_llgcr(Rdst, Rdst); // zero-extend sum %} ins_pipe(pipe_class_dummy); %} @@ -10705,27 +10735,18 @@ instruct popCountI(iRegI dst, iRegI src, iRegI tmp, flagsReg cr) %{ instruct popCountL(iRegI dst, iRegL src, iRegL tmp, flagsReg cr) %{ match(Set dst (PopCountL src)); effect(TEMP_DEF dst, TEMP tmp, KILL cr); - predicate(UsePopCountInstruction && VM_Version::has_PopCount()); + predicate(UsePopCountInstruction && + VM_Version::has_PopCount() && + (!VM_Version::has_MiscInstrExt3())); ins_cost(DEFAULT_COST); - // TODO: s390 port size(FIXED_SIZE); + size(34); format %{ "POPCNT $dst,$src\t # pop count long" %} ins_encode %{ Register Rdst = $dst$$Register; Register Rsrc = $src$$Register; Register Rtmp = $tmp$$Register; - // Prefer compile-time assertion over run-time SIGILL. - assert(VM_Version::has_PopCount(), "bad predicate for countLeadingZerosI"); - assert_different_registers(Rdst, Rtmp); - - // Original version. Using LA instead of algr seems to be a really bad idea (-35%). - __ z_popcnt(Rdst, Rsrc); - __ z_ahhlr(Rdst, Rdst, Rdst); - __ z_sllg(Rtmp, Rdst, 16); - __ z_algr(Rdst, Rtmp); - __ z_sllg(Rtmp, Rdst, 8); - __ z_algr(Rdst, Rtmp); - __ z_srlg(Rdst, Rdst, 56); + __ pop_count_long_without_ext3(Rdst, Rsrc, Rtmp); %} ins_pipe(pipe_class_dummy); %} diff --git a/src/hotspot/share/classfile/symbolTable.cpp b/src/hotspot/share/classfile/symbolTable.cpp index 1fbc49947bf13..f680939682aaa 100644 --- a/src/hotspot/share/classfile/symbolTable.cpp +++ b/src/hotspot/share/classfile/symbolTable.cpp @@ -172,7 +172,7 @@ class SymbolTableConfig : public AllStatic { // Deleting permanent symbol should not occur very often (insert race condition), // so log it. log_trace_symboltable_helper(&value, "Freeing permanent symbol"); - size_t alloc_size = _local_table->get_node_size() + value.byte_size() + value.effective_length(); + size_t alloc_size = SymbolTableHash::get_dynamic_node_size(value.byte_size()); if (!SymbolTable::arena()->Afree(memory, alloc_size)) { log_trace_symboltable_helper(&value, "Leaked permanent symbol"); } @@ -182,7 +182,7 @@ class SymbolTableConfig : public AllStatic { private: static void* allocate_node_impl(size_t size, Value const& value) { - size_t alloc_size = size + value.byte_size() + value.effective_length(); + size_t alloc_size = SymbolTableHash::get_dynamic_node_size(value.byte_size()); #if INCLUDE_CDS if (CDSConfig::is_dumping_static_archive()) { MutexLocker ml(DumpRegion_lock, Mutex::_no_safepoint_check_flag); diff --git a/src/hotspot/share/oops/symbol.hpp b/src/hotspot/share/oops/symbol.hpp index 008b979492dd9..3b3f1366203b9 100644 --- a/src/hotspot/share/oops/symbol.hpp +++ b/src/hotspot/share/oops/symbol.hpp @@ -122,7 +122,7 @@ class Symbol : public MetaspaceObj { }; static int byte_size(int length) { - // minimum number of natural words needed to hold these bits (no non-heap version) + // minimum number of bytes needed to hold these bits (no non-heap version) return (int)(sizeof(Symbol) + (length > 2 ? length - 2 : 0)); } static int size(int length) { @@ -146,8 +146,6 @@ class Symbol : public MetaspaceObj { int size() const { return size(utf8_length()); } int byte_size() const { return byte_size(utf8_length()); }; - // length without the _body - size_t effective_length() const { return (size_t)byte_size() - sizeof(Symbol); } // Symbols should be stored in the read-only region of CDS archive. static bool is_read_only_by_default() { return true; } diff --git a/src/hotspot/share/runtime/threads.cpp b/src/hotspot/share/runtime/threads.cpp index eb83dda1ff5ff..e7c425b2b8cd6 100644 --- a/src/hotspot/share/runtime/threads.cpp +++ b/src/hotspot/share/runtime/threads.cpp @@ -1321,6 +1321,15 @@ void Threads::print_on(outputStream* st, bool print_stacks, p->trace_stack(); } else { p->print_stack_on(st); + const oop thread_oop = p->threadObj(); + if (thread_oop != nullptr) { + if (p->is_vthread_mounted()) { + const oop vt = p->vthread(); + assert(vt != nullptr, "vthread should not be null when vthread is mounted"); + st->print_cr(" Mounted virtual thread \"%s\" #" INT64_FORMAT, JavaThread::name_for(vt), (int64_t)java_lang_Thread::thread_id(vt)); + p->print_vthread_stack_on(st); + } + } } } st->cr(); diff --git a/src/hotspot/share/utilities/concurrentHashTable.hpp b/src/hotspot/share/utilities/concurrentHashTable.hpp index c7d5832048c29..991ea9fe3c609 100644 --- a/src/hotspot/share/utilities/concurrentHashTable.hpp +++ b/src/hotspot/share/utilities/concurrentHashTable.hpp @@ -91,6 +91,13 @@ class ConcurrentHashTable : public CHeapObj { void print_on(outputStream* st) const {}; void print_value_on(outputStream* st) const {}; + + static bool is_dynamic_sized_value_compatible() { + // To support dynamically sized Value types, where part of the payload is + // allocated beyond the end of the object, it must be that the _value + // field ends where the Node object ends. (No end padding). + return offset_of(Node, _value) + sizeof(_value) == sizeof(Node); + } }; // Only constructed with placement new from an array allocated with MEMFLAGS @@ -419,6 +426,7 @@ class ConcurrentHashTable : public CHeapObj { size_t get_size_log2(Thread* thread); static size_t get_node_size() { return sizeof(Node); } + static size_t get_dynamic_node_size(size_t value_size); bool is_max_size_reached() { return _size_limit_reached; } // This means no paused bucket resize operation is going to resume diff --git a/src/hotspot/share/utilities/concurrentHashTable.inline.hpp b/src/hotspot/share/utilities/concurrentHashTable.inline.hpp index 2b30e664109d7..a83b6dd8a58d0 100644 --- a/src/hotspot/share/utilities/concurrentHashTable.inline.hpp +++ b/src/hotspot/share/utilities/concurrentHashTable.inline.hpp @@ -1055,6 +1055,15 @@ inline size_t ConcurrentHashTable:: return _table->_log2_size; } +template +inline size_t ConcurrentHashTable:: + get_dynamic_node_size(size_t value_size) +{ + assert(Node::is_dynamic_sized_value_compatible(), "VALUE must be compatible"); + assert(value_size >= sizeof(VALUE), "must include the VALUE"); + return sizeof(Node) - sizeof(VALUE) + value_size; +} + template inline bool ConcurrentHashTable:: shrink(Thread* thread, size_t size_limit_log2) diff --git a/src/java.base/share/classes/java/lang/foreign/MemorySegment.java b/src/java.base/share/classes/java/lang/foreign/MemorySegment.java index 6c75c0385fa78..1b378512316a6 100644 --- a/src/java.base/share/classes/java/lang/foreign/MemorySegment.java +++ b/src/java.base/share/classes/java/lang/foreign/MemorySegment.java @@ -630,6 +630,9 @@ public sealed interface MemorySegment permits AbstractMemorySegmentImpl { * {@snippet lang=java : * asSlice(offset, newSize, 1); * } + *

+ * The returned memory segment shares a region of backing memory with this segment. + * Hence, no memory will be allocated or freed by this method. * * @see #asSlice(long, long, long) * @@ -646,6 +649,9 @@ public sealed interface MemorySegment permits AbstractMemorySegmentImpl { * Returns a slice of this memory segment, at the given offset, with the provided * alignment constraint. The returned segment's address is the address of this * segment plus the given offset; its size is specified by the given argument. + *

+ * The returned memory segment shares a region of backing memory with this segment. + * Hence, no memory will be allocated or freed by this method. * * @param offset The new segment base offset (relative to the address of this segment), * specified in bytes @@ -670,6 +676,9 @@ public sealed interface MemorySegment permits AbstractMemorySegmentImpl { * {@snippet lang=java : * asSlice(offset, layout.byteSize(), layout.byteAlignment()); * } + *

+ * The returned memory segment shares a region of backing memory with this segment. + * Hence, no memory will be allocated or freed by this method. * * @see #asSlice(long, long, long) * @@ -693,6 +702,9 @@ public sealed interface MemorySegment permits AbstractMemorySegmentImpl { * {@snippet lang=java : * asSlice(offset, byteSize() - offset); * } + *

+ * The returned memory segment shares a region of backing memory with this segment. + * Hence, no memory will be allocated or freed by this method. * * @see #asSlice(long, long) * @@ -706,6 +718,9 @@ public sealed interface MemorySegment permits AbstractMemorySegmentImpl { /** * Returns a new memory segment that has the same address and scope as this segment, * but with the provided size. + *

+ * The returned memory segment shares a region of backing memory with this segment. + * Hence, no memory will be allocated or freed by this method. * * @param newSize the size of the returned segment * @return a new memory segment that has the same address and scope as @@ -741,6 +756,9 @@ public sealed interface MemorySegment permits AbstractMemorySegmentImpl { * That is, the cleanup action receives a segment that is associated with the global * scope, and is accessible from any thread. The size of the segment accepted by the * cleanup action is {@link #byteSize()}. + *

+ * The returned memory segment shares a region of backing memory with this segment. + * Hence, no memory will be allocated or freed by this method. * * @apiNote The cleanup action (if present) should take care not to leak the received * segment to external clients that might access the segment after its @@ -786,6 +804,9 @@ public sealed interface MemorySegment permits AbstractMemorySegmentImpl { * That is, the cleanup action receives a segment that is associated with the global * scope, and is accessible from any thread. The size of the segment accepted by the * cleanup action is {@code newSize}. + *

+ * The returned memory segment shares a region of backing memory with this segment. + * Hence, no memory will be allocated or freed by this method. * * @apiNote The cleanup action (if present) should take care not to leak the received * segment to external clients that might access the segment after its diff --git a/src/java.base/share/classes/javax/crypto/spec/RC5ParameterSpec.java b/src/java.base/share/classes/javax/crypto/spec/RC5ParameterSpec.java index 7e4aab20e683d..67daf8bb19ab5 100644 --- a/src/java.base/share/classes/javax/crypto/spec/RC5ParameterSpec.java +++ b/src/java.base/share/classes/javax/crypto/spec/RC5ParameterSpec.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1998, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -37,9 +37,7 @@ * size, and optionally an initialization vector (IV) (only in feedback mode). * *

This class can be used to initialize a {@code Cipher} object that - * implements the RC5 algorithm as supplied by - * RSA Security LLC, - * or any parties authorized by RSA Security. + * implements the RC5 algorithm. * * @author Jan Luehe * diff --git a/src/java.desktop/macosx/classes/sun/lwawt/macosx/CPlatformResponder.java b/src/java.desktop/macosx/classes/sun/lwawt/macosx/CPlatformResponder.java index f14c7b40cbab2..0501bbfab0320 100644 --- a/src/java.desktop/macosx/classes/sun/lwawt/macosx/CPlatformResponder.java +++ b/src/java.desktop/macosx/classes/sun/lwawt/macosx/CPlatformResponder.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -87,7 +87,7 @@ void handleMouseEvent(int eventType, int modifierFlags, int buttonNumber, jmodifiers |= MouseEvent.getMaskForButton(jbuttonNumber); } - boolean jpopupTrigger = NSEvent.isPopupTrigger(jmodifiers); + boolean jpopupTrigger = NSEvent.isPopupTrigger(jmodifiers, jeventType); eventNotifier.notifyMouseEvent(jeventType, System.currentTimeMillis(), jbuttonNumber, x, y, absX, absY, jmodifiers, jclickCount, diff --git a/src/java.desktop/macosx/classes/sun/lwawt/macosx/CTrayIcon.java b/src/java.desktop/macosx/classes/sun/lwawt/macosx/CTrayIcon.java index d0b9288aeae1c..7d35f859a7a3d 100644 --- a/src/java.desktop/macosx/classes/sun/lwawt/macosx/CTrayIcon.java +++ b/src/java.desktop/macosx/classes/sun/lwawt/macosx/CTrayIcon.java @@ -253,7 +253,7 @@ private void handleMouseEvent(NSEvent nsEvent) { int jmodifiers = NSEvent.nsToJavaModifiers( nsEvent.getModifierFlags()); - boolean isPopupTrigger = NSEvent.isPopupTrigger(jmodifiers); + boolean isPopupTrigger = NSEvent.isPopupTrigger(jmodifiers, jeventType); int eventButtonMask = (jbuttonNumber > 0)? MouseEvent.getMaskForButton(jbuttonNumber) : 0; diff --git a/src/java.desktop/macosx/classes/sun/lwawt/macosx/NSEvent.java b/src/java.desktop/macosx/classes/sun/lwawt/macosx/NSEvent.java index ca3d9ad5da7ea..f3fcfdfb43cfe 100644 --- a/src/java.desktop/macosx/classes/sun/lwawt/macosx/NSEvent.java +++ b/src/java.desktop/macosx/classes/sun/lwawt/macosx/NSEvent.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -269,7 +269,12 @@ static int nsToJavaEventType(int nsEventType) { */ static native char nsToJavaChar(char nsChar, int modifierFlags, boolean spaceKeyTyped); - static boolean isPopupTrigger(int jmodifiers) { + static boolean isPopupTrigger(int jmodifiers, int jeventType) { + if (jeventType != MouseEvent.MOUSE_PRESSED + && jeventType != MouseEvent.MOUSE_RELEASED) { + return false; + } + final boolean isRightButtonDown = ((jmodifiers & InputEvent.BUTTON3_DOWN_MASK) != 0); final boolean isLeftButtonDown = ((jmodifiers & InputEvent.BUTTON1_DOWN_MASK) != 0); final boolean isControlDown = ((jmodifiers & InputEvent.CTRL_DOWN_MASK) != 0); diff --git a/src/java.net.http/share/classes/jdk/internal/net/http/ResponseSubscribers.java b/src/java.net.http/share/classes/jdk/internal/net/http/ResponseSubscribers.java index ed95f815913ce..09ad87f9205eb 100644 --- a/src/java.net.http/share/classes/jdk/internal/net/http/ResponseSubscribers.java +++ b/src/java.net.http/share/classes/jdk/internal/net/http/ResponseSubscribers.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -356,6 +356,7 @@ public void onNext(List items) { // incoming buffers are allocated by http client internally, // and won't be used anywhere except this place. // So it's free simply to store them for further processing. + Objects.requireNonNull(items); // ensure NPE is thrown before assert assert Utils.hasRemaining(items); received.addAll(items); } diff --git a/src/java.net.http/share/classes/jdk/internal/net/http/common/Utils.java b/src/java.net.http/share/classes/jdk/internal/net/http/common/Utils.java index 9bd7f35028638..b2eb570db0c1e 100644 --- a/src/java.net.http/share/classes/jdk/internal/net/http/common/Utils.java +++ b/src/java.net.http/share/classes/jdk/internal/net/http/common/Utils.java @@ -746,6 +746,7 @@ public static String stringOf(Collection source) { } public static long remaining(ByteBuffer[] bufs) { + if (bufs == null) return 0; long remain = 0; for (ByteBuffer buf : bufs) { remain += buf.remaining(); @@ -754,6 +755,7 @@ public static long remaining(ByteBuffer[] bufs) { } public static boolean hasRemaining(List bufs) { + if (bufs == null) return false; for (ByteBuffer buf : bufs) { if (buf.hasRemaining()) return true; @@ -762,6 +764,7 @@ public static boolean hasRemaining(List bufs) { } public static boolean hasRemaining(ByteBuffer[] bufs) { + if (bufs == null) return false; for (ByteBuffer buf : bufs) { if (buf.hasRemaining()) return true; @@ -770,6 +773,7 @@ public static boolean hasRemaining(ByteBuffer[] bufs) { } public static long remaining(List bufs) { + if (bufs == null) return 0L; long remain = 0; for (ByteBuffer buf : bufs) { remain += buf.remaining(); @@ -778,12 +782,14 @@ public static long remaining(List bufs) { } public static long synchronizedRemaining(List bufs) { + if (bufs == null) return 0L; synchronized (bufs) { return remaining(bufs); } } - public static int remaining(List bufs, int max) { + public static long remaining(List bufs, long max) { + if (bufs == null) return 0; long remain = 0; for (ByteBuffer buf : bufs) { remain += buf.remaining(); @@ -794,7 +800,13 @@ public static int remaining(List bufs, int max) { return (int) remain; } - public static int remaining(ByteBuffer[] refs, int max) { + public static int remaining(List bufs, int max) { + // safe cast since max is an int + return (int) remaining(bufs, (long) max); + } + + public static long remaining(ByteBuffer[] refs, long max) { + if (refs == null) return 0; long remain = 0; for (ByteBuffer b : refs) { remain += b.remaining(); @@ -805,6 +817,11 @@ public static int remaining(ByteBuffer[] refs, int max) { return (int) remain; } + public static int remaining(ByteBuffer[] refs, int max) { + // safe cast since max is an int + return (int) remaining(refs, (long) max); + } + public static void close(Closeable... closeables) { for (Closeable c : closeables) { try { diff --git a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WinMsiBundler.java b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WinMsiBundler.java index 894d41d764204..c0ae65b3b0bcc 100644 --- a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WinMsiBundler.java +++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WinMsiBundler.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -67,6 +67,7 @@ import static jdk.jpackage.internal.StandardBundlerParam.TEMP_ROOT; import static jdk.jpackage.internal.StandardBundlerParam.VENDOR; import static jdk.jpackage.internal.StandardBundlerParam.VERSION; +import jdk.jpackage.internal.WixToolset.WixToolsetType; import org.w3c.dom.Document; import org.w3c.dom.NodeList; import org.xml.sax.SAXException; @@ -253,7 +254,7 @@ public String getBundleType() { public boolean supported(boolean platformInstaller) { try { if (wixToolset == null) { - wixToolset = WixTool.toolset(); + wixToolset = WixTool.createToolset(); } return true; } catch (ConfigException ce) { @@ -300,7 +301,7 @@ public boolean validate(Map params) appImageBundler.validate(params); if (wixToolset == null) { - wixToolset = WixTool.toolset(); + wixToolset = WixTool.createToolset(); } try { @@ -309,16 +310,17 @@ public boolean validate(Map params) throw new ConfigException(ex); } - for (var toolInfo: wixToolset.values()) { + for (var tool : wixToolset.getType().getTools()) { Log.verbose(MessageFormat.format(I18N.getString( - "message.tool-version"), toolInfo.path.getFileName(), - toolInfo.version)); + "message.tool-version"), wixToolset.getToolPath(tool). + getFileName(), wixToolset.getVersion())); } - wixFragments.forEach(wixFragment -> wixFragment.setWixVersion( - wixToolset.get(WixTool.Light).version)); + wixFragments.forEach(wixFragment -> wixFragment.setWixVersion(wixToolset.getVersion(), + wixToolset.getType())); - wixFragments.get(0).logWixFeatures(); + wixFragments.stream().map(WixFragmentBuilder::getLoggableWixFeatures).flatMap( + List::stream).distinct().toList().forEach(Log::verbose); /********* validate bundle parameters *************/ @@ -512,22 +514,6 @@ private Map prepareMainProjectFile( data.put("JpIsSystemWide", "yes"); } - // Copy standard l10n files. - for (String loc : Arrays.asList("de", "en", "ja", "zh_CN")) { - String fname = "MsiInstallerStrings_" + loc + ".wxl"; - createResource(fname, params) - .setCategory(I18N.getString("resource.wxl-file")) - .saveToFile(configDir.resolve(fname)); - } - - createResource("main.wxs", params) - .setCategory(I18N.getString("resource.main-wix-file")) - .saveToFile(configDir.resolve("main.wxs")); - - createResource("overrides.wxi", params) - .setCategory(I18N.getString("resource.overrides-wix-file")) - .saveToFile(configDir.resolve("overrides.wxi")); - return data; } @@ -542,13 +528,11 @@ private Path buildMSI(Map params, .toString())); WixPipeline wixPipeline = new WixPipeline() - .setToolset(wixToolset.entrySet().stream().collect( - Collectors.toMap( - entry -> entry.getKey(), - entry -> entry.getValue().path))) - .setWixObjDir(TEMP_ROOT.fetchFrom(params).resolve("wixobj")) - .setWorkDir(WIN_APP_IMAGE.fetchFrom(params)) - .addSource(CONFIG_ROOT.fetchFrom(params).resolve("main.wxs"), wixVars); + .setToolset(wixToolset) + .setWixObjDir(TEMP_ROOT.fetchFrom(params).resolve("wixobj")) + .setWorkDir(WIN_APP_IMAGE.fetchFrom(params)) + .addSource(CONFIG_ROOT.fetchFrom(params).resolve("main.wxs"), + wixVars); for (var wixFragment : wixFragments) { wixFragment.configureWixPipeline(wixPipeline); @@ -557,16 +541,46 @@ private Path buildMSI(Map params, Log.verbose(MessageFormat.format(I18N.getString( "message.generating-msi"), msiOut.toAbsolutePath().toString())); - wixPipeline.addLightOptions("-sice:ICE27"); + switch (wixToolset.getType()) { + case Wix3 -> { + wixPipeline.addLightOptions("-sice:ICE27"); + + if (!MSI_SYSTEM_WIDE.fetchFrom(params)) { + wixPipeline.addLightOptions("-sice:ICE91"); + } + } + case Wix4 -> { + } + default -> { + throw new IllegalArgumentException(); + } + } + + final Path configDir = CONFIG_ROOT.fetchFrom(params); + + var primaryWxlFiles = Stream.of("de", "en", "ja", "zh_CN").map(loc -> { + return configDir.resolve("MsiInstallerStrings_" + loc + ".wxl"); + }).toList(); + + var wixResources = new WixSourceConverter.ResourceGroup(wixToolset.getType()); - if (!MSI_SYSTEM_WIDE.fetchFrom(params)) { - wixPipeline.addLightOptions("-sice:ICE91"); + // Copy standard l10n files. + for (var path : primaryWxlFiles) { + var name = path.getFileName().toString(); + wixResources.addResource(createResource(name, params).setPublicName(name).setCategory( + I18N.getString("resource.wxl-file")), path); } + wixResources.addResource(createResource("main.wxs", params).setPublicName("main.wxs"). + setCategory(I18N.getString("resource.main-wix-file")), configDir.resolve("main.wxs")); + + wixResources.addResource(createResource("overrides.wxi", params).setPublicName( + "overrides.wxi").setCategory(I18N.getString("resource.overrides-wix-file")), + configDir.resolve("overrides.wxi")); + // Filter out custom l10n files that were already used to // override primary l10n files. Ignore case filename comparison, // both lists are expected to be short. - List primaryWxlFiles = getWxlFilesFromDir(params, CONFIG_ROOT); List customWxlFiles = getWxlFilesFromDir(params, RESOURCE_DIR).stream() .filter(custom -> primaryWxlFiles.stream().noneMatch(primary -> primary.getFileName().toString().equalsIgnoreCase( @@ -577,6 +591,17 @@ private Path buildMSI(Map params, custom.getFileName().toString()))) .toList(); + // Copy custom l10n files. + for (var path : customWxlFiles) { + var name = path.getFileName().toString(); + wixResources.addResource(createResource(name, params).setPublicName(name). + setSourceOrder(OverridableResource.Source.ResourceDir).setCategory(I18N. + getString("resource.wxl-file")), configDir.resolve(name)); + } + + // Save all WiX resources into config dir. + wixResources.saveResources(); + // All l10n files are supplied to WiX with "-loc", but only // Cultures from custom files and a single primary Culture are // included into "-cultures" list @@ -586,6 +611,7 @@ private Path buildMSI(Map params, List cultures = new ArrayList<>(); for (var wxl : customWxlFiles) { + wxl = configDir.resolve(wxl.getFileName()); wixPipeline.addLightOptions("-loc", wxl.toAbsolutePath().normalize().toString()); cultures.add(getCultureFromWxlFile(wxl)); } @@ -598,8 +624,20 @@ private Path buildMSI(Map params, // Build ordered list of unique cultures. Set uniqueCultures = new LinkedHashSet<>(); uniqueCultures.addAll(cultures); - wixPipeline.addLightOptions(uniqueCultures.stream().collect( - Collectors.joining(";", "-cultures:", ""))); + switch (wixToolset.getType()) { + case Wix3 -> { + wixPipeline.addLightOptions(uniqueCultures.stream().collect(Collectors.joining(";", + "-cultures:", ""))); + } + case Wix4 -> { + uniqueCultures.forEach(culture -> { + wixPipeline.addLightOptions("-culture", culture); + }); + } + default -> { + throw new IllegalArgumentException(); + } + } wixPipeline.buildMsi(msiOut.toAbsolutePath()); @@ -751,7 +789,7 @@ private static OverridableResource initServiceInstallerResource( } private Path installerIcon; - private Map wixToolset; + private WixToolset wixToolset; private AppImageBundler appImageBundler; private final List wixFragments; } diff --git a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixAppImageFragmentBuilder.java b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixAppImageFragmentBuilder.java index cf7338f7d0b48..5bc20c1413c86 100644 --- a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixAppImageFragmentBuilder.java +++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixAppImageFragmentBuilder.java @@ -64,6 +64,7 @@ import static jdk.jpackage.internal.WinMsiBundler.MSI_SYSTEM_WIDE; import static jdk.jpackage.internal.WinMsiBundler.SERVICE_INSTALLER; import static jdk.jpackage.internal.WinMsiBundler.WIN_APP_IMAGE; +import jdk.jpackage.internal.WixToolset.WixToolsetType; import org.w3c.dom.NodeList; /** @@ -152,6 +153,16 @@ void addFilesToConfigRoot() throws IOException { super.addFilesToConfigRoot(); } + @Override + List getLoggableWixFeatures() { + if (isWithWix36Features()) { + return List.of(MessageFormat.format(I18N.getString("message.use-wix36-features"), + getWixVersion())); + } else { + return List.of(); + } + } + @Override protected Collection getFragmentWriters() { return List.of( @@ -314,12 +325,25 @@ boolean isFile() { return cfg.isFile; } - static void startElement(XMLStreamWriter xml, String componentId, + static void startElement(WixToolsetType wixType, XMLStreamWriter xml, String componentId, String componentGuid) throws XMLStreamException, IOException { xml.writeStartElement("Component"); - xml.writeAttribute("Win64", is64Bit() ? "yes" : "no"); + switch (wixType) { + case Wix3 -> { + xml.writeAttribute("Win64", is64Bit() ? "yes" : "no"); + xml.writeAttribute("Guid", componentGuid); + } + case Wix4 -> { + xml.writeAttribute("Bitness", is64Bit() ? "always64" : "always32"); + if (!componentGuid.equals("*")) { + xml.writeAttribute("Guid", componentGuid); + } + } + default -> { + throw new IllegalArgumentException(); + } + } xml.writeAttribute("Id", componentId); - xml.writeAttribute("Guid", componentGuid); } private static final class Config { @@ -370,22 +394,31 @@ private String addComponent(XMLStreamWriter xml, Path path, directoryRefPath = path; } - xml.writeStartElement("DirectoryRef"); - xml.writeAttribute("Id", Id.Folder.of(directoryRefPath)); + startDirectoryElement(xml, "DirectoryRef", directoryRefPath); final String componentId = "c" + role.idOf(path); - Component.startElement(xml, componentId, String.format("{%s}", - role.guidOf(path))); + Component.startElement(getWixType(), xml, componentId, String.format( + "{%s}", role.guidOf(path))); if (role == Component.Shortcut) { - xml.writeStartElement("Condition"); String property = shortcutFolders.stream().filter(shortcutFolder -> { return path.startsWith(shortcutFolder.root); }).map(shortcutFolder -> { return shortcutFolder.property; }).findFirst().get(); - xml.writeCharacters(property); - xml.writeEndElement(); + switch (getWixType()) { + case Wix3 -> { + xml.writeStartElement("Condition"); + xml.writeCharacters(property); + xml.writeEndElement(); + } + case Wix4 -> { + xml.writeAttribute("Condition", property); + } + default -> { + throw new IllegalArgumentException(); + } + } } boolean isRegistryKeyPath = !systemWide || role.isRegistryKeyPath(); @@ -442,7 +475,7 @@ private void addFaComponentGroup(XMLStreamWriter xml) private void addShortcutComponentGroup(XMLStreamWriter xml) throws XMLStreamException, IOException { List componentIds = new ArrayList<>(); - Set defineShortcutFolders = new HashSet<>(); + Set defineShortcutFolders = new HashSet<>(); for (var launcher : launchers) { for (var folder : shortcutFolders) { Path launcherPath = addExeSuffixToPath(installedAppImage @@ -457,16 +490,27 @@ private void addShortcutComponentGroup(XMLStreamWriter xml) throws folder); if (componentId != null) { - defineShortcutFolders.add(folder); + Path folderPath = folder.getPath(this); + boolean defineFolder; + switch (getWixType()) { + case Wix3 -> + defineFolder = true; + case Wix4 -> + defineFolder = !SYSTEM_DIRS.contains(folderPath); + default -> + throw new IllegalArgumentException(); + } + if (defineFolder) { + defineShortcutFolders.add(folderPath); + } componentIds.add(componentId); } } } } - for (var folder : defineShortcutFolders) { - Path path = folder.getPath(this); - componentIds.addAll(addRootBranch(xml, path)); + for (var folderPath : defineShortcutFolders) { + componentIds.addAll(addRootBranch(xml, folderPath)); } addComponentGroup(xml, "Shortcuts", componentIds); @@ -546,13 +590,18 @@ private List addRootBranch(XMLStreamWriter xml, Path path) throw throwInvalidPathException(path); } - Function createDirectoryName = dir -> null; - boolean sysDir = true; - int levels = 1; + int levels; var dirIt = path.iterator(); - xml.writeStartElement("DirectoryRef"); - xml.writeAttribute("Id", dirIt.next().toString()); + + if (getWixType() != WixToolsetType.Wix3 && TARGETDIR.equals(path.getName(0))) { + levels = 0; + dirIt.next(); + } else { + levels = 1; + xml.writeStartElement("DirectoryRef"); + xml.writeAttribute("Id", dirIt.next().toString()); + } path = path.getName(0); while (dirIt.hasNext()) { @@ -562,21 +611,11 @@ private List addRootBranch(XMLStreamWriter xml, Path path) if (sysDir && !SYSTEM_DIRS.contains(path)) { sysDir = false; - createDirectoryName = dir -> dir.getFileName().toString(); } - final String directoryId; - if (!sysDir && path.equals(installDir)) { - directoryId = INSTALLDIR.toString(); - } else { - directoryId = Id.Folder.of(path); - } - xml.writeStartElement("Directory"); - xml.writeAttribute("Id", directoryId); - - String directoryName = createDirectoryName.apply(path); - if (directoryName != null) { - xml.writeAttribute("Name", directoryName); + startDirectoryElement(xml, "Directory", path); + if (!sysDir) { + xml.writeAttribute("Name", path.getFileName().toString()); } } @@ -584,9 +623,37 @@ private List addRootBranch(XMLStreamWriter xml, Path path) xml.writeEndElement(); } - List componentIds = new ArrayList<>(); + return List.of(); + } - return componentIds; + private void startDirectoryElement(XMLStreamWriter xml, String wix3ElementName, Path path) throws XMLStreamException { + final String elementName; + switch (getWixType()) { + case Wix3 -> { + elementName = wix3ElementName; + } + case Wix4 -> { + if (SYSTEM_DIRS.contains(path)) { + elementName = "StandardDirectory"; + } else { + elementName = wix3ElementName; + } + } + default -> { + throw new IllegalArgumentException(); + } + + } + + final String directoryId; + if (path.equals(installDir)) { + directoryId = INSTALLDIR.toString(); + } else { + directoryId = Id.Folder.of(path); + } + + xml.writeStartElement(elementName); + xml.writeAttribute("Id", directoryId); } private String addRemoveDirectoryComponent(XMLStreamWriter xml, Path path) @@ -785,7 +852,7 @@ private void addRegistryKeyPath(XMLStreamWriter xml, Path path, xml.writeStartElement("RegistryKey"); xml.writeAttribute("Root", regRoot); xml.writeAttribute("Key", registryKeyPath); - if (DottedVersion.compareComponents(getWixVersion(), DottedVersion.lazy("3.6")) < 0) { + if (!isWithWix36Features()) { xml.writeAttribute("Action", "createAndRemoveOnUninstall"); } xml.writeStartElement("RegistryValue"); @@ -799,7 +866,7 @@ private void addRegistryKeyPath(XMLStreamWriter xml, Path path, private String addDirectoryCleaner(XMLStreamWriter xml, Path path) throws XMLStreamException, IOException { - if (DottedVersion.compareComponents(getWixVersion(), DottedVersion.lazy("3.6")) < 0) { + if (!isWithWix36Features()) { return null; } @@ -821,14 +888,13 @@ private String addDirectoryCleaner(XMLStreamWriter xml, Path path) throws xml.writeStartElement("DirectoryRef"); xml.writeAttribute("Id", INSTALLDIR.toString()); - Component.startElement(xml, componentId, "*"); + Component.startElement(getWixType(), xml, componentId, "*"); addRegistryKeyPath(xml, INSTALLDIR, () -> propertyId, () -> { return toWixPath(path); }); - xml.writeStartElement( - "http://schemas.microsoft.com/wix/UtilExtension", + xml.writeStartElement(getWixNamespaces().get(WixNamespace.Util), "RemoveFolderEx"); xml.writeAttribute("On", "uninstall"); xml.writeAttribute("Property", propertyId); @@ -839,6 +905,10 @@ private String addDirectoryCleaner(XMLStreamWriter xml, Path path) throws return componentId; } + private boolean isWithWix36Features() { + return DottedVersion.compareComponents(getWixVersion(), DottedVersion.greedy("3.6")) >= 0; + } + // Does the following conversions: // INSTALLDIR -> [INSTALLDIR] // TARGETDIR/ProgramFiles64Folder/foo/bar -> [ProgramFiles64Folder]foo/bar diff --git a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixFragmentBuilder.java b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixFragmentBuilder.java index bc98899c65985..0276cc96e6521 100644 --- a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixFragmentBuilder.java +++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixFragmentBuilder.java @@ -29,31 +29,35 @@ import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.nio.file.Path; -import java.text.MessageFormat; -import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Optional; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.xml.stream.XMLStreamWriter; import jdk.jpackage.internal.IOUtils.XmlConsumer; import jdk.jpackage.internal.OverridableResource.Source; -import static jdk.jpackage.internal.OverridableResource.createResource; import static jdk.jpackage.internal.StandardBundlerParam.CONFIG_ROOT; import jdk.internal.util.Architecture; +import static jdk.jpackage.internal.OverridableResource.createResource; +import jdk.jpackage.internal.WixSourceConverter.ResourceGroup; +import jdk.jpackage.internal.WixToolset.WixToolsetType; /** * Creates WiX fragment. */ abstract class WixFragmentBuilder { - void setWixVersion(DottedVersion v) { - wixVersion = v; + final void setWixVersion(DottedVersion version, WixToolsetType type) { + Objects.requireNonNull(version); + Objects.requireNonNull(type); + wixVersion = version; + wixType = type; } - void setOutputFileName(String v) { + final void setOutputFileName(String v) { outputFileName = v; } @@ -65,11 +69,8 @@ void initFromParams(Map params) { Source.ResourceDir); } - void logWixFeatures() { - if (DottedVersion.compareComponents(wixVersion, DottedVersion.lazy("3.6")) >= 0) { - Log.verbose(MessageFormat.format(I18N.getString( - "message.use-wix36-features"), wixVersion)); - } + List getLoggableWixFeatures() { + return List.of(); } void configureWixPipeline(WixPipeline wixPipeline) { @@ -91,52 +92,84 @@ void addFilesToConfigRoot() throws IOException { } if (additionalResources != null) { - for (var resource : additionalResources) { - resource.resource.saveToFile(configRoot.resolve( - resource.saveAsName)); - } + additionalResources.saveResources(); } } - DottedVersion getWixVersion() { + final WixToolsetType getWixType() { + return wixType; + } + + final DottedVersion getWixVersion() { return wixVersion; } + protected static enum WixNamespace { + Default, + Util; + } + + final protected Map getWixNamespaces() { + switch (wixType) { + case Wix3 -> { + return Map.of(WixNamespace.Default, + "http://schemas.microsoft.com/wix/2006/wi", + WixNamespace.Util, + "http://schemas.microsoft.com/wix/UtilExtension"); + } + case Wix4 -> { + return Map.of(WixNamespace.Default, + "http://wixtoolset.org/schemas/v4/wxs", + WixNamespace.Util, + "http://wixtoolset.org/schemas/v4/wxs/util"); + } + default -> { + throw new IllegalArgumentException(); + } + + } + } + static boolean is64Bit() { return Architecture.is64bit(); } - protected Path getConfigRoot() { + final protected Path getConfigRoot() { return configRoot; } protected abstract Collection getFragmentWriters(); - protected void defineWixVariable(String variableName) { + final protected void defineWixVariable(String variableName) { setWixVariable(variableName, "yes"); } - protected void setWixVariable(String variableName, String variableValue) { + final protected void setWixVariable(String variableName, String variableValue) { if (wixVariables == null) { wixVariables = new WixVariables(); } wixVariables.setWixVariable(variableName, variableValue); } - protected void addResource(OverridableResource resource, String saveAsName) { + final protected void addResource(OverridableResource resource, String saveAsName) { if (additionalResources == null) { - additionalResources = new ArrayList<>(); + additionalResources = new ResourceGroup(getWixType()); } - additionalResources.add(new ResourceWithName(resource, saveAsName)); + additionalResources.addResource(resource, configRoot.resolve(saveAsName)); } - static void createWixSource(Path file, XmlConsumer xmlConsumer) - throws IOException { + private void createWixSource(Path file, XmlConsumer xmlConsumer) throws IOException { IOUtils.createXml(file, xml -> { xml.writeStartElement("Wix"); - xml.writeDefaultNamespace("http://schemas.microsoft.com/wix/2006/wi"); - xml.writeNamespace("util", - "http://schemas.microsoft.com/wix/UtilExtension"); + for (var ns : getWixNamespaces().entrySet()) { + switch (ns.getKey()) { + case Default -> + xml.writeDefaultNamespace(ns.getValue()); + default -> + xml.writeNamespace(ns.getKey().name().toLowerCase(), ns. + getValue()); + } + } xmlConsumer.accept((XMLStreamWriter) Proxy.newProxyInstance( XMLStreamWriter.class.getClassLoader(), new Class[]{ @@ -146,16 +179,6 @@ static void createWixSource(Path file, XmlConsumer xmlConsumer) }); } - private static class ResourceWithName { - - ResourceWithName(OverridableResource resource, String saveAsName) { - this.resource = resource; - this.saveAsName = saveAsName; - } - private final OverridableResource resource; - private final String saveAsName; - } - private static class WixPreprocessorEscaper implements InvocationHandler { WixPreprocessorEscaper(XMLStreamWriter target) { @@ -208,9 +231,10 @@ private String escape(CharSequence str) { private final XMLStreamWriter target; } + private WixToolsetType wixType; private DottedVersion wixVersion; private WixVariables wixVariables; - private List additionalResources; + private ResourceGroup additionalResources; private OverridableResource fragmentResource; private String outputFileName; private Path configRoot; diff --git a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixPipeline.java b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixPipeline.java index 58a07b6cbafd8..835247ed1debb 100644 --- a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixPipeline.java +++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixPipeline.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -22,18 +22,19 @@ * or visit www.oracle.com if you need additional information or have any * questions. */ - package jdk.jpackage.internal; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; -import java.util.HashMap; +import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Objects; -import java.util.function.UnaryOperator; +import java.util.Optional; +import java.util.function.Function; +import java.util.stream.Collectors; import java.util.stream.Stream; /** @@ -45,7 +46,7 @@ public class WixPipeline { lightOptions = new ArrayList<>(); } - WixPipeline setToolset(Map v) { + WixPipeline setToolset(WixToolset v) { toolset = v; return this; } @@ -79,13 +80,92 @@ WixPipeline addLightOptions(String ... v) { } void buildMsi(Path msi) throws IOException { + Objects.requireNonNull(workDir); + + switch (toolset.getType()) { + case Wix3 -> buildMsiWix3(msi); + case Wix4 -> buildMsiWix4(msi); + default -> throw new IllegalArgumentException(); + } + } + + private void addWixVariblesToCommandLine( + Map otherWixVariables, List cmdline) { + Stream.of(wixVariables, Optional.ofNullable(otherWixVariables). + orElseGet(Collections::emptyMap)).filter(Objects::nonNull). + reduce((a, b) -> { + a.putAll(b); + return a; + }).ifPresent(wixVars -> { + var entryStream = wixVars.entrySet().stream(); + + Stream stream; + switch (toolset.getType()) { + case Wix3 -> { + stream = entryStream.map(wixVar -> { + return String.format("-d%s=%s", wixVar.getKey(), wixVar. + getValue()); + }); + } + case Wix4 -> { + stream = entryStream.map(wixVar -> { + return Stream.of("-d", String.format("%s=%s", wixVar. + getKey(), wixVar.getValue())); + }).flatMap(Function.identity()); + } + default -> { + throw new IllegalArgumentException(); + } + } + + stream.reduce(cmdline, (ctnr, wixVar) -> { + ctnr.add(wixVar); + return ctnr; + }, (x, y) -> { + x.addAll(y); + return x; + }); + }); + } + + private void buildMsiWix4(Path msi) throws IOException { + var mergedSrcWixVars = sources.stream().map(wixSource -> { + return Optional.ofNullable(wixSource.variables).orElseGet( + Collections::emptyMap).entrySet().stream(); + }).flatMap(Function.identity()).collect(Collectors.toMap( + Map.Entry::getKey, Map.Entry::getValue)); + + List cmdline = new ArrayList<>(List.of( + toolset.getToolPath(WixTool.Wix4).toString(), + "build", + "-nologo", + "-pdbtype", "none", + "-intermediatefolder", wixObjDir.toAbsolutePath().toString(), + "-ext", "WixToolset.Util.wixext", + "-arch", WixFragmentBuilder.is64Bit() ? "x64" : "x86" + )); + + cmdline.addAll(lightOptions); + + addWixVariblesToCommandLine(mergedSrcWixVars, cmdline); + + cmdline.addAll(sources.stream().map(wixSource -> { + return wixSource.source.toAbsolutePath().toString(); + }).toList()); + + cmdline.addAll(List.of("-out", msi.toString())); + + execute(cmdline); + } + + private void buildMsiWix3(Path msi) throws IOException { List wixObjs = new ArrayList<>(); for (var source : sources) { - wixObjs.add(compile(source)); + wixObjs.add(compileWix3(source)); } List lightCmdline = new ArrayList<>(List.of( - toolset.get(WixTool.Light).toString(), + toolset.getToolPath(WixTool.Light3).toString(), "-nologo", "-spdb", "-ext", "WixUtilExtension", @@ -99,31 +179,20 @@ void buildMsi(Path msi) throws IOException { execute(lightCmdline); } - private Path compile(WixSource wixSource) throws IOException { - UnaryOperator adjustPath = path -> { - return workDir != null ? path.toAbsolutePath() : path; - }; - - Path wixObj = adjustPath.apply(wixObjDir).resolve(IOUtils.replaceSuffix( + private Path compileWix3(WixSource wixSource) throws IOException { + Path wixObj = wixObjDir.toAbsolutePath().resolve(IOUtils.replaceSuffix( IOUtils.getFileName(wixSource.source), ".wixobj")); List cmdline = new ArrayList<>(List.of( - toolset.get(WixTool.Candle).toString(), + toolset.getToolPath(WixTool.Candle3).toString(), "-nologo", - adjustPath.apply(wixSource.source).toString(), + wixSource.source.toAbsolutePath().toString(), "-ext", "WixUtilExtension", "-arch", WixFragmentBuilder.is64Bit() ? "x64" : "x86", "-out", wixObj.toAbsolutePath().toString() )); - Map appliedVaribales = new HashMap<>(); - Stream.of(wixVariables, wixSource.variables) - .filter(Objects::nonNull) - .forEachOrdered(appliedVaribales::putAll); - - appliedVaribales.entrySet().stream().map(wixVar -> String.format("-d%s=%s", - wixVar.getKey(), wixVar.getValue())).forEachOrdered( - cmdline::add); + addWixVariblesToCommandLine(wixSource.variables, cmdline); execute(cmdline); @@ -131,8 +200,8 @@ private Path compile(WixSource wixSource) throws IOException { } private void execute(List cmdline) throws IOException { - Executor.of(new ProcessBuilder(cmdline).directory( - workDir != null ? workDir.toFile() : null)).executeExpectSuccess(); + Executor.of(new ProcessBuilder(cmdline).directory(workDir.toFile())). + executeExpectSuccess(); } private static final class WixSource { @@ -140,7 +209,7 @@ private static final class WixSource { Map variables; } - private Map toolset; + private WixToolset toolset; private Map wixVariables; private List lightOptions; private Path wixObjDir; diff --git a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixSourceConverter.java b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixSourceConverter.java new file mode 100644 index 0000000000000..7786d64a78693 --- /dev/null +++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixSourceConverter.java @@ -0,0 +1,420 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.jpackage.internal; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.util.AbstractMap; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.function.Supplier; +import java.util.stream.Collectors; +import javax.xml.XMLConstants; +import javax.xml.stream.XMLOutputFactory; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamWriter; +import javax.xml.transform.Source; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stax.StAXResult; +import javax.xml.transform.stream.StreamSource; +import jdk.jpackage.internal.WixToolset.WixToolsetType; +import org.w3c.dom.Document; +import org.xml.sax.SAXException; + +/** + * Converts WiX v3 source file into WiX v4 format. + */ +final class WixSourceConverter { + + enum Status { + SavedAsIs, + SavedAsIsMalfromedXml, + Transformed, + } + + WixSourceConverter(Path resourceDir) throws IOException { + var buf = new ByteArrayOutputStream(); + + new OverridableResource("wix3-to-wix4-conv.xsl") + .setPublicName("wix-conv.xsl") + .setResourceDir(resourceDir) + .setCategory(I18N.getString("resource.wix-src-conv")) + .saveToStream(buf); + + var xslt = new StreamSource(new ByteArrayInputStream(buf.toByteArray())); + + var tf = TransformerFactory.newInstance(); + try { + this.transformer = tf.newTransformer(xslt); + } catch (TransformerException ex) { + // Should never happen + throw new RuntimeException(ex); + } + + this.outputFactory = XMLOutputFactory.newInstance(); + } + + Status appyTo(OverridableResource resource, Path resourceSaveAsFile) throws IOException { + // Save the resource into DOM tree and read xml namespaces from it. + // If some namespaces are not recognized by this converter, save the resource as is. + // If all detected namespaces are recognized, run transformation of the DOM tree and save + // output into destination file. + + var buf = saveResourceInMemory(resource); + + Document inputXmlDom; + try { + inputXmlDom = IOUtils.initDocumentBuilder().parse(new ByteArrayInputStream(buf)); + } catch (SAXException ex) { + // Malformed XML, don't run converter, save as is. + resource.saveToFile(resourceSaveAsFile); + return Status.SavedAsIsMalfromedXml; + } + + try { + var nc = new NamespaceCollector(); + TransformerFactory.newInstance().newTransformer(). + transform(new DOMSource(inputXmlDom), new StAXResult((XMLStreamWriter) Proxy. + newProxyInstance(XMLStreamWriter.class.getClassLoader(), + new Class[]{XMLStreamWriter.class}, nc))); + if (!nc.isOnlyKnownNamespacesUsed()) { + // Unsupported namespaces detected in input XML, don't run converter, save as is. + resource.saveToFile(resourceSaveAsFile); + return Status.SavedAsIs; + } + } catch (TransformerException ex) { + // Should never happen + throw new RuntimeException(ex); + } + + Supplier inputXml = () -> { + // Should be "new DOMSource(inputXmlDom)", but no transfromation is applied in this case! + return new StreamSource(new ByteArrayInputStream(buf)); + }; + + var nc = new NamespaceCollector(); + try { + // Run transfomation to collect namespaces from the output XML. + transformer.transform(inputXml.get(), new StAXResult((XMLStreamWriter) Proxy. + newProxyInstance(XMLStreamWriter.class.getClassLoader(), + new Class[]{XMLStreamWriter.class}, nc))); + } catch (TransformerException ex) { + // Should never happen + throw new RuntimeException(ex); + } + + try (var outXml = new ByteArrayOutputStream()) { + transformer.transform(inputXml.get(), new StAXResult((XMLStreamWriter) Proxy. + newProxyInstance(XMLStreamWriter.class.getClassLoader(), + new Class[]{XMLStreamWriter.class}, new NamespaceCleaner(nc. + getPrefixToUri(), outputFactory.createXMLStreamWriter(outXml))))); + Files.createDirectories(IOUtils.getParent(resourceSaveAsFile)); + Files.copy(new ByteArrayInputStream(outXml.toByteArray()), resourceSaveAsFile, + StandardCopyOption.REPLACE_EXISTING); + } catch (TransformerException | XMLStreamException ex) { + // Should never happen + throw new RuntimeException(ex); + } + + return Status.Transformed; + } + + private static byte[] saveResourceInMemory(OverridableResource resource) throws IOException { + var buf = new ByteArrayOutputStream(); + resource.saveToStream(buf); + return buf.toByteArray(); + } + + final static class ResourceGroup { + + ResourceGroup(WixToolsetType wixToolsetType) { + this.wixToolsetType = wixToolsetType; + } + + void addResource(OverridableResource resource, Path resourceSaveAsFile) { + resources.put(resourceSaveAsFile, resource); + } + + void saveResources() throws IOException { + switch (wixToolsetType) { + case Wix3 -> { + for (var e : resources.entrySet()) { + e.getValue().saveToFile(e.getKey()); + } + } + case Wix4 -> { + var resourceDir = resources.values().stream().filter(res -> { + return null != res.getResourceDir(); + }).findAny().map(OverridableResource::getResourceDir).orElse(null); + var conv = new WixSourceConverter(resourceDir); + for (var e : resources.entrySet()) { + conv.appyTo(e.getValue(), e.getKey()); + } + } + default -> { + throw new IllegalArgumentException(); + } + } + } + + private final Map resources = new HashMap<>(); + private final WixToolsetType wixToolsetType; + } + + // + // Default JDK XSLT v1.0 processor is not handling well default namespace mappings. + // Running generic template: + // + // + // + // + // + // + // + // produces: + // + // + // + // + // ... + // + // + // + // which is conformant XML but WiX4 doesn't like it: + // + // wix.exe : error WIX0202: The {http://wixtoolset.org/schemas/v4/wxl}String element contains an unsupported extension attribute '{http://www.w3.org/2000/xmlns/}ns1'. The {http://wixtoolset.org/schemas/v4/wxl}String element does not currently support extension attributes. Is the {http://www.w3.org/2000/xmlns/}ns1 attribute using the correct XML namespace? + // wix.exe : error WIX0202: The {http://wixtoolset.org/schemas/v4/wxl}String element contains an unsupported extension attribute '{http://www.w3.org/2000/xmlns/}ns2'. The {http://wixtoolset.org/schemas/v4/wxl}String element does not currently support extension attributes. Is the {http://www.w3.org/2000/xmlns/}ns2 attribute using the correct XML namespace? + // wix.exe : error WIX0202: The {http://wixtoolset.org/schemas/v4/wxl}String element contains an unsupported extension attribute '{http://www.w3.org/2000/xmlns/}ns3'. The {http://wixtoolset.org/schemas/v4/wxl}String element does not currently support extension attributes. Is the {http://www.w3.org/2000/xmlns/}ns3 attribute using the correct XML namespace? + // + // Someone hit this issue long ago - https://stackoverflow.com/questions/26904623/replace-default-namespace-using-xsl and they suggested to use different XSLT processor. + // Two online XSLT processors used in testing produce clean XML with this template indeed: + // + // + // + // + // ... + // + // + // + // To workaround default JDK's XSLT processor limitations we do additionl postprocessing of output XML with NamespaceCleaner class. + // + private static class NamespaceCleaner implements InvocationHandler { + + NamespaceCleaner(Map prefixToUri, XMLStreamWriter target) { + this.uriToPrefix = prefixToUri.entrySet().stream().collect(Collectors.toMap( + Map.Entry::getValue, e -> { + return new Prefix(e.getKey()); + }, (x, y) -> x)); + this.prefixToUri = prefixToUri; + this.target = target; + } + + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + switch (method.getName()) { + case "writeNamespace" -> { + final String uri = (String) args[1]; + var prefixObj = uriToPrefix.get(uri); + if (!prefixObj.written) { + prefixObj.written = true; + target.writeNamespace(prefixObj.name, uri); + } + return null; + } + case "writeStartElement", "writeEmptyElement" -> { + final String name; + switch (args.length) { + case 1 -> + name = (String) args[0]; + case 2, 3 -> + name = (String) args[1]; + default -> + throw new IllegalArgumentException(); + } + + final String prefix; + final String localName; + final String[] tokens = name.split(":", 2); + if (tokens.length == 2) { + prefix = tokens[0]; + localName = tokens[1]; + } else { + localName = name; + switch (args.length) { + case 3 -> + prefix = (String) args[0]; + case 2 -> + prefix = uriToPrefix.get((String) args[0]).name; + default -> + prefix = null; + } + } + + if (prefix != null && !XMLConstants.DEFAULT_NS_PREFIX.equals(prefix)) { + final String uri = prefixToUri.get(prefix); + var prefixObj = uriToPrefix.get(uri); + if (prefixObj.written) { + var writeName = String.join(":", prefixObj.name, localName); + if ("writeStartElement".equals(method.getName())) { + target.writeStartElement(writeName); + } else { + target.writeEmptyElement(writeName); + } + return null; + } else { + prefixObj.written = (args.length > 1); + args = Arrays.copyOf(args, args.length, Object[].class); + if (localName.equals(name)) { + // No prefix in the name + if (args.length == 3) { + args[0] = prefixObj.name; + } + } else { + var writeName = String.join(":", prefixObj.name, localName); + switch (args.length) { + case 1 -> + args[0] = writeName; + case 2 -> { + args[0] = uri; + args[1] = writeName; + } + case 3 -> { + args[0] = prefixObj.name; + args[1] = writeName; + args[2] = uri; + } + } + } + } + } + } + } + + return method.invoke(target, args); + } + + static class Prefix { + + Prefix(String name) { + this.name = name; + } + + private final String name; + private boolean written; + } + + private final Map uriToPrefix; + private final Map prefixToUri; + private final XMLStreamWriter target; + } + + private static class NamespaceCollector implements InvocationHandler { + + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + switch (method.getName()) { + case "setPrefix", "writeNamespace" -> { + var prefix = (String) args[0]; + var namespace = prefixToUri.computeIfAbsent(prefix, k -> createValue(args[1])); + if (XMLConstants.XMLNS_ATTRIBUTE.equals(prefix)) { + namespace.setValue(true); + } + } + case "writeStartElement", "writeEmptyElement" -> { + switch (args.length) { + case 3 -> + prefixToUri.computeIfAbsent((String) args[0], k -> createValue( + (String) args[2])).setValue(true); + case 2 -> + initFromElementName((String) args[1], (String) args[0]); + case 1 -> + initFromElementName((String) args[0], null); + } + } + } + return null; + } + + boolean isOnlyKnownNamespacesUsed() { + return prefixToUri.values().stream().filter(namespace -> { + return namespace.getValue(); + }).allMatch(namespace -> { + if (!namespace.getValue()) { + return true; + } else { + return KNOWN_NAMESPACES.contains(namespace.getKey()); + } + }); + } + + Map getPrefixToUri() { + return prefixToUri.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, + e -> { + return e.getValue().getKey(); + })); + } + + private void initFromElementName(String name, String namespace) { + final String[] tokens = name.split(":", 2); + if (tokens.length == 2) { + if (namespace != null) { + prefixToUri.computeIfAbsent(tokens[0], k -> createValue(namespace)).setValue( + true); + } else { + prefixToUri.computeIfPresent(tokens[0], (k, v) -> { + v.setValue(true); + return v; + }); + } + } + } + + private Map.Entry createValue(Object prefix) { + return new AbstractMap.SimpleEntry((String) prefix, false); + } + + private final Map> prefixToUri = new HashMap<>(); + } + + private final Transformer transformer; + private final XMLOutputFactory outputFactory; + + // The list of WiX v3 namespaces this converter can handle + private final static Set KNOWN_NAMESPACES = Set.of( + "http://schemas.microsoft.com/wix/2006/localization", + "http://schemas.microsoft.com/wix/2006/wi"); +} diff --git a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixTool.java b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixTool.java index 68104444b3c11..f16b28edf2448 100644 --- a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixTool.java +++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixTool.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -22,7 +22,6 @@ * or visit www.oracle.com if you need additional information or have any * questions. */ - package jdk.jpackage.internal; import java.io.IOException; @@ -32,105 +31,198 @@ import java.nio.file.Path; import java.nio.file.PathMatcher; import java.text.MessageFormat; -import java.util.Collections; import java.util.Comparator; -import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Optional; -import java.util.function.Supplier; +import java.util.Set; +import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.Stream; +import jdk.jpackage.internal.WixToolset.WixToolsetType; /** * WiX tool. */ public enum WixTool { - Candle, Light; + Candle3("candle", DottedVersion.lazy("3.0")), + Light3("light", DottedVersion.lazy("3.0")), + Wix4("wix", DottedVersion.lazy("4.0.4")); + + WixTool(String commandName, DottedVersion minimalVersion) { + this.toolFileName = IOUtils.addSuffix(Path.of(commandName), ".exe"); + this.minimalVersion = minimalVersion; + } static final class ToolInfo { + ToolInfo(Path path, String version) { this.path = path; - this.version = new DottedVersion(version); + this.version = DottedVersion.lazy(version); } final Path path; final DottedVersion version; } - static Map toolset() throws ConfigException { - Map toolset = new HashMap<>(); - for (var tool : values()) { - toolset.put(tool, tool.find()); - } - return toolset; - } + static WixToolset createToolset() throws ConfigException { + Function, Map> conv = lookupResults -> { + return lookupResults.stream().filter(ToolLookupResult::isValid).collect(Collectors. + groupingBy(lookupResult -> { + return lookupResult.getInfo().version.toString(); + })).values().stream().filter(sameVersionLookupResults -> { + Set sameVersionTools = sameVersionLookupResults.stream().map( + ToolLookupResult::getTool).collect(Collectors.toSet()); + if (sameVersionTools.equals(Set.of(Candle3)) || sameVersionTools.equals(Set.of( + Light3))) { + // There is only one tool from WiX v3 toolset of some version available. Discard it. + return false; + } else { + return true; + } + }).flatMap(List::stream).collect(Collectors.toMap(ToolLookupResult::getTool, + ToolLookupResult::getInfo, (ToolInfo x, ToolInfo y) -> { + return Stream.of(x, y).sorted(Comparator.comparing((ToolInfo toolInfo) -> { + return toolInfo.version.toComponentsString(); + }).reversed()).findFirst().get(); + })); + }; - ToolInfo find() throws ConfigException { - final Path toolFileName = IOUtils.addSuffix( - Path.of(name().toLowerCase()), ".exe"); + Function, Optional> createToolset = lookupResults -> { + var tools = conv.apply(lookupResults); + // Try to build a toolset found in the PATH and in known locations. + return Stream.of(WixToolsetType.values()).map(toolsetType -> { + return WixToolset.create(toolsetType.getTools(), tools); + }).filter(Objects::nonNull).findFirst(); + }; - String[] version = new String[1]; - ConfigException reason = createToolValidator(toolFileName, version).get(); - if (version[0] != null) { - if (reason == null) { - // Found in PATH. - return new ToolInfo(toolFileName, version[0]); - } + var toolsInPath = Stream.of(values()).map(tool -> { + return new ToolLookupResult(tool, null); + }).toList(); - // Found in PATH, but something went wrong. - throw reason; + // Try to build a toolset from tools in the PATH first. + var toolset = createToolset.apply(toolsInPath); + if (toolset.isPresent()) { + return toolset.get(); } - for (var dir : findWixInstallDirs()) { - Path path = dir.resolve(toolFileName); - if (Files.exists(path)) { - reason = createToolValidator(path, version).get(); - if (reason != null) { - throw reason; + // Look up for WiX tools in known locations. + var toolsInKnownWiXDirs = findWixInstallDirs().stream().map(dir -> { + return Stream.of(values()).map(tool -> { + return new ToolLookupResult(tool, dir); + }); + }).flatMap(Function.identity()).toList(); + + // Build a toolset found in the PATH and in known locations. + var allFoundTools = Stream.of(toolsInPath, toolsInKnownWiXDirs).flatMap(List::stream).filter( + ToolLookupResult::isValid).toList(); + toolset = createToolset.apply(allFoundTools); + if (toolset.isPresent()) { + return toolset.get(); + } else if (allFoundTools.isEmpty()) { + throw new ConfigException(I18N.getString("error.no-wix-tools"), I18N.getString( + "error.no-wix-tools.advice")); + } else { + var toolOldVerErr = allFoundTools.stream().map(lookupResult -> { + if (lookupResult.versionTooOld) { + return new ConfigException(MessageFormat.format(I18N.getString( + "message.wrong-tool-version"), lookupResult.getInfo().path, + lookupResult.getInfo().version, lookupResult.getTool().minimalVersion), + I18N.getString("error.no-wix-tools.advice")); + } else { + return null; } - return new ToolInfo(path, version[0]); + }).filter(Objects::nonNull).findAny(); + if (toolOldVerErr.isPresent()) { + throw toolOldVerErr.get(); + } else { + throw new ConfigException(I18N.getString("error.no-wix-tools"), I18N.getString( + "error.no-wix-tools.advice")); } } - - throw reason; } - private static Supplier createToolValidator(Path toolPath, - String[] versionCtnr) { - return new ToolValidator(toolPath) - .setCommandLine("/?") - .setMinimalVersion(MINIMAL_VERSION) - .setToolNotFoundErrorHandler( - (name, ex) -> new ConfigException( - I18N.getString("error.no-wix-tools"), - I18N.getString("error.no-wix-tools.advice"))) - .setToolOldVersionErrorHandler( - (name, version) -> new ConfigException( - MessageFormat.format(I18N.getString( - "message.wrong-tool-version"), name, - version, MINIMAL_VERSION), - I18N.getString("error.no-wix-tools.advice"))) - .setVersionParser(output -> { - versionCtnr[0] = ""; + private static class ToolLookupResult { + + ToolLookupResult(WixTool tool, Path lookupDir) { + + final Path toolPath = Optional.ofNullable(lookupDir).map(p -> p.resolve( + tool.toolFileName)).orElse(tool.toolFileName); + + final boolean[] tooOld = new boolean[1]; + final String[] parsedVersion = new String[1]; + + final var validator = new ToolValidator(toolPath).setMinimalVersion(tool.minimalVersion). + setToolNotFoundErrorHandler((name, ex) -> { + return new ConfigException("", ""); + }).setToolOldVersionErrorHandler((name, version) -> { + tooOld[0] = true; + return null; + }); + + final Function, String> versionParser; + + if (Set.of(Candle3, Light3).contains(tool)) { + validator.setCommandLine("/?"); + versionParser = output -> { String firstLineOfOutput = output.findFirst().orElse(""); int separatorIdx = firstLineOfOutput.lastIndexOf(' '); if (separatorIdx == -1) { return null; } - versionCtnr[0] = firstLineOfOutput.substring(separatorIdx + 1); - return versionCtnr[0]; - })::validate; - } + return firstLineOfOutput.substring(separatorIdx + 1); + }; + } else { + validator.setCommandLine("--version"); + versionParser = output -> { + return output.findFirst().orElse(""); + }; + } - private static final DottedVersion MINIMAL_VERSION = DottedVersion.lazy("3.0"); + validator.setVersionParser(output -> { + parsedVersion[0] = versionParser.apply(output); + return parsedVersion[0]; + }); - static Path getSystemDir(String envVar, String knownDir) { + this.tool = tool; + if (validator.validate() == null) { + // Tool found + this.versionTooOld = tooOld[0]; + this.info = new ToolInfo(toolPath, parsedVersion[0]); + } else { + this.versionTooOld = false; + this.info = null; + } + } + + WixTool getTool() { + return tool; + } + + ToolInfo getInfo() { + return info; + } + + boolean isValid() { + return info != null && !versionTooOld; + } + + boolean isVersionTooOld() { + return versionTooOld; + } + + private final WixTool tool; + private final ToolInfo info; + private final boolean versionTooOld; + } + + private static Path getSystemDir(String envVar, String knownDir) { return Optional .ofNullable(getEnvVariableAsPath(envVar)) .orElseGet(() -> Optional - .ofNullable(getEnvVariableAsPath("SystemDrive")) - .orElseGet(() -> Path.of("C:")).resolve(knownDir)); + .ofNullable(getEnvVariableAsPath("SystemDrive")) + .orElseGet(() -> Path.of("C:")).resolve(knownDir)); } private static Path getEnvVariableAsPath(String envVar) { @@ -147,8 +239,22 @@ private static Path getEnvVariableAsPath(String envVar) { } private static List findWixInstallDirs() { - PathMatcher wixInstallDirMatcher = FileSystems.getDefault().getPathMatcher( - "glob:WiX Toolset v*"); + return Stream.of(findWixCurrentInstallDirs(), findWix3InstallDirs()). + flatMap(List::stream).toList(); + } + + private static List findWixCurrentInstallDirs() { + return Stream.of(getEnvVariableAsPath("USERPROFILE"), Optional.ofNullable(System. + getProperty("user.home")).map(Path::of).orElse(null)).filter(Objects::nonNull).map( + path -> { + return path.resolve(".dotnet/tools"); + }).filter(Files::isDirectory).distinct().toList(); + } + + private static List findWix3InstallDirs() { + PathMatcher wixInstallDirMatcher = FileSystems.getDefault(). + getPathMatcher( + "glob:WiX Toolset v*"); Path programFiles = getSystemDir("ProgramFiles", "\\Program Files"); Path programFilesX86 = getSystemDir("ProgramFiles(x86)", @@ -157,18 +263,20 @@ private static List findWixInstallDirs() { // Returns list of WiX install directories ordered by WiX version number. // Newer versions go first. return Stream.of(programFiles, programFilesX86).map(path -> { - List result; try (var paths = Files.walk(path, 1)) { - result = paths.toList(); + return paths.toList(); } catch (IOException ex) { Log.verbose(ex); - result = Collections.emptyList(); + List empty = List.of(); + return empty; } - return result; }).flatMap(List::stream) - .filter(path -> wixInstallDirMatcher.matches(path.getFileName())) - .sorted(Comparator.comparing(Path::getFileName).reversed()) - .map(path -> path.resolve("bin")) - .toList(); + .filter(path -> wixInstallDirMatcher.matches(path.getFileName())). + sorted(Comparator.comparing(Path::getFileName).reversed()) + .map(path -> path.resolve("bin")) + .toList(); } + + private final Path toolFileName; + private final DottedVersion minimalVersion; } diff --git a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixToolset.java b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixToolset.java new file mode 100644 index 0000000000000..ab433616f44a6 --- /dev/null +++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixToolset.java @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.jpackage.internal; + +import java.nio.file.Path; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +final class WixToolset { + + static enum WixToolsetType { + // Wix v4+ + Wix4(WixTool.Wix4), + // Wix v3+ + Wix3(WixTool.Candle3, WixTool.Light3); + + WixToolsetType(WixTool... tools) { + this.tools = Set.of(tools); + } + + Set getTools() { + return tools; + } + + private final Set tools; + } + + private WixToolset(Map tools) { + this.tools = tools; + } + + WixToolsetType getType() { + return Stream.of(WixToolsetType.values()).filter(toolsetType -> { + return toolsetType.getTools().equals(tools.keySet()); + }).findAny().get(); + } + + Path getToolPath(WixTool tool) { + return tools.get(tool).path; + } + + DottedVersion getVersion() { + return tools.values().iterator().next().version; + } + + static WixToolset create(Set requiredTools, Map allTools) { + var filteredTools = allTools.entrySet().stream().filter(e -> { + return requiredTools.contains(e.getKey()); + }).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + if (filteredTools.keySet().equals(requiredTools)) { + return new WixToolset(filteredTools); + } else { + return null; + } + } + + private final Map tools; +} diff --git a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixUiFragmentBuilder.java b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixUiFragmentBuilder.java index 8d20d6432bf3a..4f39a65e3b6ad 100644 --- a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixUiFragmentBuilder.java +++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixUiFragmentBuilder.java @@ -43,6 +43,7 @@ import static jdk.jpackage.internal.OverridableResource.createResource; import static jdk.jpackage.internal.StandardBundlerParam.LICENSE_FILE; import jdk.jpackage.internal.WixAppImageFragmentBuilder.ShortcutsFolder; +import jdk.jpackage.internal.WixToolset.WixToolsetType; /** * Creates UI WiX fragment. @@ -100,7 +101,13 @@ void configureWixPipeline(WixPipeline wixPipeline) { super.configureWixPipeline(wixPipeline); if (withShortcutPromptDlg || withInstallDirChooserDlg || withLicenseDlg) { - wixPipeline.addLightOptions("-ext", "WixUIExtension"); + final String extName; + switch (getWixType()) { + case Wix3 -> extName = "WixUIExtension"; + case Wix4 -> extName = "WixToolset.UI.wixext"; + default -> throw new IllegalArgumentException(); + } + wixPipeline.addLightOptions("-ext", extName); } // Only needed if we using CA dll, so Wix can find it @@ -148,15 +155,14 @@ private void addUI(XMLStreamWriter xml) throws XMLStreamException, xml.writeEndElement(); // WixVariable } - xml.writeStartElement("UI"); - xml.writeAttribute("Id", "JpUI"); - var ui = getUI(); if (ui != null) { - ui.write(this, xml); + ui.write(getWixType(), this, xml); + } else { + xml.writeStartElement("UI"); + xml.writeAttribute("Id", "JpUI"); + xml.writeEndElement(); } - - xml.writeEndElement(); // UI } private UI getUI() { @@ -187,12 +193,43 @@ private enum UI { this.dialogPairsSupplier = dialogPairsSupplier; } - void write(WixUiFragmentBuilder outer, XMLStreamWriter xml) throws - XMLStreamException, IOException { - xml.writeStartElement("UIRef"); - xml.writeAttribute("Id", wixUIRef); - xml.writeEndElement(); // UIRef + void write(WixToolsetType wixType, WixUiFragmentBuilder outer, XMLStreamWriter xml) throws XMLStreamException, IOException { + switch (wixType) { + case Wix3 -> {} + case Wix4 -> { + // https://wixtoolset.org/docs/fourthree/faqs/#converting-custom-wixui-dialog-sets + xml.writeProcessingInstruction("foreach WIXUIARCH in X86;X64;A64"); + writeWix4UIRef(xml, wixUIRef, "JpUIInternal_$(WIXUIARCH)"); + xml.writeProcessingInstruction("endforeach"); + writeWix4UIRef(xml, "JpUIInternal", "JpUI"); + } + default -> { + throw new IllegalArgumentException(); + } + } + + xml.writeStartElement("UI"); + switch (wixType) { + case Wix3 -> { + xml.writeAttribute("Id", "JpUI"); + xml.writeStartElement("UIRef"); + xml.writeAttribute("Id", wixUIRef); + xml.writeEndElement(); // UIRef + } + case Wix4 -> { + xml.writeAttribute("Id", "JpUIInternal"); + } + default -> { + throw new IllegalArgumentException(); + } + } + writeContents(wixType, outer, xml); + xml.writeEndElement(); // UI + } + + private void writeContents(WixToolsetType wixType, WixUiFragmentBuilder outer, + XMLStreamWriter xml) throws XMLStreamException, IOException { if (dialogIdsSupplier != null) { List

dialogIds = dialogIdsSupplier.apply(outer); Map> dialogPairs = dialogPairsSupplier.get(); @@ -210,7 +247,7 @@ void write(WixUiFragmentBuilder outer, XMLStreamWriter xml) throws DialogPair pair = new DialogPair(firstId, secondId); for (var curPair : List.of(pair, pair.flip())) { for (var publish : dialogPairs.get(curPair)) { - writePublishDialogPair(xml, publish, curPair); + writePublishDialogPair(wixType, xml, publish, curPair); } } firstId = secondId; @@ -218,6 +255,17 @@ void write(WixUiFragmentBuilder outer, XMLStreamWriter xml) throws } } + private static void writeWix4UIRef(XMLStreamWriter xml, String uiRef, String id) throws XMLStreamException, IOException { + // https://wixtoolset.org/docs/fourthree/faqs/#referencing-the-standard-wixui-dialog-sets + xml.writeStartElement("UI"); + xml.writeAttribute("Id", id); + xml.writeStartElement("ui:WixUI"); + xml.writeAttribute("Id", uiRef); + xml.writeNamespace("ui", "http://wixtoolset.org/schemas/v4/wxs/ui"); + xml.writeEndElement(); // UIRef + xml.writeEndElement(); // UI + } + private final String wixUIRef; private final Function> dialogIdsSupplier; private final Supplier>> dialogPairsSupplier; @@ -441,9 +489,8 @@ private static PublishBuilder buildPublish(Publish publish) { return new PublishBuilder(publish); } - private static void writePublishDialogPair(XMLStreamWriter xml, - Publish publish, DialogPair dialogPair) throws IOException, - XMLStreamException { + private static void writePublishDialogPair(WixToolsetType wixType, XMLStreamWriter xml, + Publish publish, DialogPair dialogPair) throws IOException, XMLStreamException { xml.writeStartElement("Publish"); xml.writeAttribute("Dialog", dialogPair.firstId); xml.writeAttribute("Control", publish.control); @@ -452,7 +499,11 @@ private static void writePublishDialogPair(XMLStreamWriter xml, if (publish.order != 0) { xml.writeAttribute("Order", String.valueOf(publish.order)); } - xml.writeCharacters(publish.condition); + switch (wixType) { + case Wix3 -> xml.writeCharacters(publish.condition); + case Wix4 -> xml.writeAttribute("Condition", publish.condition); + default -> throw new IllegalArgumentException(); + } xml.writeEndElement(); } @@ -463,9 +514,8 @@ private final class CustomDialog { this.wxsFileName = wxsFileName; this.wixVariables = new WixVariables(); - addResource( - createResource(wxsFileName, params).setCategory(category), - wxsFileName); + addResource(createResource(wxsFileName, params).setCategory(category).setPublicName( + wxsFileName), wxsFileName); } void addToWixPipeline(WixPipeline wixPipeline) { diff --git a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/InstallDirNotEmptyDlg.wxs b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/InstallDirNotEmptyDlg.wxs index 936984b1fff92..755b2a0aab9e2 100644 --- a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/InstallDirNotEmptyDlg.wxs +++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/InstallDirNotEmptyDlg.wxs @@ -1,7 +1,7 @@ - + diff --git a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/wix3-to-wix4-conv.xsl b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/wix3-to-wix4-conv.xsl new file mode 100644 index 0000000000000..382ed731b5a6f --- /dev/null +++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/wix3-to-wix4-conv.xsl @@ -0,0 +1,183 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/hotspot/jtreg/ProblemList.txt b/test/hotspot/jtreg/ProblemList.txt index 78e09c0627afa..5290a9bb6260e 100644 --- a/test/hotspot/jtreg/ProblemList.txt +++ b/test/hotspot/jtreg/ProblemList.txt @@ -67,8 +67,6 @@ compiler/floatingpoint/TestSubnormalDouble.java 8317810 generic-i586 compiler/startup/StartupOutput.java 8326615 generic-x64 -compiler/rangechecks/TestArrayAccessAboveRCAfterRCCastIIEliminated.java 8332369 generic-all - compiler/codecache/CodeCacheFullCountTest.java 8332954 generic-all ############################################################################# @@ -169,7 +167,7 @@ vmTestbase/vm/mlvm/meth/stress/jdi/breakpointInCompiledCode/Test.java 8257761 ge vmTestbase/vm/mlvm/indy/func/jvmti/mergeCP_indy2none_a/TestDescription.java 8013267 generic-all vmTestbase/vm/mlvm/indy/func/jvmti/mergeCP_indy2manyDiff_b/TestDescription.java 8013267 generic-all vmTestbase/vm/mlvm/indy/func/jvmti/mergeCP_indy2manySame_b/TestDescription.java 8013267 generic-all -vmTestbase/vm/mlvm/meth/stress/compiler/deoptimize/Test.java#id1 8325905 generic-all +vmTestbase/vm/mlvm/meth/stress/compiler/deoptimize/Test.java#id1 8324756 generic-all vmTestbase/nsk/jdwp/ThreadReference/ForceEarlyReturn/forceEarlyReturn001/forceEarlyReturn001.java 7199837 generic-all diff --git a/test/hotspot/jtreg/compiler/c2/irTests/TestArrLenCheckOptimization.java b/test/hotspot/jtreg/compiler/c2/irTests/TestArrLenCheckOptimization.java new file mode 100644 index 0000000000000..cb955bf14e7c8 --- /dev/null +++ b/test/hotspot/jtreg/compiler/c2/irTests/TestArrLenCheckOptimization.java @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2024, Arm Limited. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package compiler.c2.irTests; + +import compiler.lib.ir_framework.*; +import jdk.test.lib.Asserts; + +/* + * @test + * @bug 8321308 + * @summary AArch64: Fix matching predication for cbz/cbnz + * @requires os.arch=="aarch64" + * @library /test/lib / + * @run driver compiler.c2.irTests.TestArrLenCheckOptimization + */ + +public class TestArrLenCheckOptimization { + + int result = 0; + + @Test + @IR(counts = {IRNode.CBZW_LS, "1"}) + void test_le_int(int ia[]) { + result += ia[0]; + } + + @Test + @IR(counts = {IRNode.CBNZW_HI, "1"}) + void test_gt_int(int ia[]) { + if (ia.length > 0) { + result += 0x88; + } else { + result -= 1; + } + } + + @Test + @IR(counts = {IRNode.CBZ_LS, "1"}) + void test_le_long(int ia[]) { + if (Long.compareUnsigned(ia.length, 0) > 0) { + result += 0x80; + } else { + result -= 1; + } + } + + @Test + @IR(counts = {IRNode.CBZ_HI, "1"}) + void test_gt_long(int ia[]) { + if (Long.compareUnsigned(ia.length, 0) > 0) { + result += 0x82; + } else { + result -= 1; + } + } + + @Run(test = {"test_le_int", "test_gt_int", "test_le_long", "test_gt_long"}, + mode = RunMode.STANDALONE) + public void test_runner() { + for (int i = 0; i < 10_000; i++) { + test_le_int(new int[1]); + test_gt_int(new int[0]); + test_le_long(new int[1]); + test_gt_long(new int[0]); + } + } + + public static void main(String [] args) { + TestFramework.run(); + } + } diff --git a/test/hotspot/jtreg/compiler/lib/ir_framework/IRNode.java b/test/hotspot/jtreg/compiler/lib/ir_framework/IRNode.java index 1fb68439afbb2..efa7ccadfda1b 100644 --- a/test/hotspot/jtreg/compiler/lib/ir_framework/IRNode.java +++ b/test/hotspot/jtreg/compiler/lib/ir_framework/IRNode.java @@ -378,6 +378,26 @@ public class IRNode { beforeMatchingNameRegex(CAST_LL, "CastLL"); } + public static final String CBNZW_HI = PREFIX + "CBNZW_HI" + POSTFIX; + static { + optoOnly(CBNZW_HI, "cbwhi"); + } + + public static final String CBZW_LS = PREFIX + "CBZW_LS" + POSTFIX; + static { + optoOnly(CBZW_LS, "cbwls"); + } + + public static final String CBZ_LS = PREFIX + "CBZ_LS" + POSTFIX; + static { + optoOnly(CBZ_LS, "cbls"); + } + + public static final String CBZ_HI = PREFIX + "CBZ_HI" + POSTFIX; + static { + optoOnly(CBZ_HI, "cbhi"); + } + public static final String CHECKCAST_ARRAY = PREFIX + "CHECKCAST_ARRAY" + POSTFIX; static { String regex = "(((?i:cmp|CLFI|CLR).*precise \\[.*:|.*(?i:mov|mv|or).*precise \\[.*:.*\\R.*(cmp|CMP|CLR))" + END; diff --git a/test/hotspot/jtreg/compiler/sharedstubs/SharedStubToInterpTest.java b/test/hotspot/jtreg/compiler/sharedstubs/SharedStubToInterpTest.java index 5dd938442cc82..2bf8afae60926 100644 --- a/test/hotspot/jtreg/compiler/sharedstubs/SharedStubToInterpTest.java +++ b/test/hotspot/jtreg/compiler/sharedstubs/SharedStubToInterpTest.java @@ -36,6 +36,7 @@ * @requires vm.opt.TieredStopAtLevel == null & vm.opt.TieredCompilation == null * @requires vm.simpleArch == "x86" | vm.simpleArch == "x64" | vm.simpleArch == "aarch64" | vm.simpleArch == "riscv64" * @requires vm.debug + * @requires !vm.graal.enabled * @run driver compiler.sharedstubs.SharedStubToInterpTest -XX:-TieredCompilation * */ diff --git a/test/hotspot/jtreg/serviceability/dcmd/thread/PrintMountedVirtualThread.java b/test/hotspot/jtreg/serviceability/dcmd/thread/PrintMountedVirtualThread.java new file mode 100644 index 0000000000000..bffe4c266a478 --- /dev/null +++ b/test/hotspot/jtreg/serviceability/dcmd/thread/PrintMountedVirtualThread.java @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import jdk.test.lib.dcmd.CommandExecutor; +import jdk.test.lib.dcmd.JMXExecutor; +import jdk.test.lib.process.OutputAnalyzer; +import org.junit.Test; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.regex.Pattern; + +/* + * @test + * @summary Test of diagnostic command Thread.print with virtual threads + * @requires vm.continuations + * @library /test/lib + * @run junit PrintMountedVirtualThread + */ +public class PrintMountedVirtualThread { + + public void run(CommandExecutor executor) throws InterruptedException { + var shouldFinish = new AtomicBoolean(false); + var started = new CountDownLatch(1); + final Runnable runnable = new DummyRunnable(shouldFinish, started); + try { + Thread vthread = Thread.ofVirtual().name("Dummy Vthread").start(runnable); + started.await(); + /* Execute */ + OutputAnalyzer output = executor.execute("Thread.print"); + output.shouldMatch(".*at " + Pattern.quote(DummyRunnable.class.getName()) + "\\.run.*"); + output.shouldMatch(".*at " + Pattern.quote(DummyRunnable.class.getName()) + "\\.compute.*"); + output.shouldMatch("Mounted virtual thread " + "\"Dummy Vthread\"" + " #" + vthread.threadId()); + + } finally { + shouldFinish.set(true); + } + } + + @Test + public void jmx() throws InterruptedException { + run(new JMXExecutor()); + } + + static class DummyRunnable implements Runnable { + + private final AtomicBoolean shouldFinish; + private final CountDownLatch started; + + public DummyRunnable(AtomicBoolean shouldFinish, CountDownLatch started) { + this.shouldFinish = shouldFinish; + this.started = started; + } + + public void run() { + compute(); + } + + void compute() { + started.countDown(); + while (!shouldFinish.get()) { + Thread.onSpinWait(); + } + } + } + + +} diff --git a/test/jdk/java/net/httpclient/BodySubscribersTest.java b/test/jdk/java/net/httpclient/BodySubscribersTest.java index 508cc95abb05e..5ee7ed4ef626e 100644 --- a/test/jdk/java/net/httpclient/BodySubscribersTest.java +++ b/test/jdk/java/net/httpclient/BodySubscribersTest.java @@ -24,7 +24,7 @@ /* * @test * @summary Basic test for the standard BodySubscribers default behavior - * @bug 8225583 + * @bug 8225583 8334028 * @run testng BodySubscribersTest */ diff --git a/test/jdk/java/net/httpclient/whitebox/java.net.http/jdk/internal/net/http/SSLTubeTest.java b/test/jdk/java/net/httpclient/whitebox/java.net.http/jdk/internal/net/http/SSLTubeTest.java index ac251f9ba7d96..114110344a407 100644 --- a/test/jdk/java/net/httpclient/whitebox/java.net.http/jdk/internal/net/http/SSLTubeTest.java +++ b/test/jdk/java/net/httpclient/whitebox/java.net.http/jdk/internal/net/http/SSLTubeTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -85,16 +85,17 @@ private static class SSLLoopbackSubscriber implements FlowTube { ExecutorService exec, CountDownLatch allBytesReceived) throws IOException { SSLServerSocketFactory fac = ctx.getServerSocketFactory(); + InetAddress loopback = InetAddress.getLoopbackAddress(); SSLServerSocket serv = (SSLServerSocket) fac.createServerSocket(); serv.setReuseAddress(false); - serv.bind(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0)); + serv.bind(new InetSocketAddress(loopback, 0)); SSLParameters params = serv.getSSLParameters(); params.setApplicationProtocols(new String[]{"proto2"}); serv.setSSLParameters(params); int serverPort = serv.getLocalPort(); - clientSock = new Socket("localhost", serverPort); + clientSock = new Socket(loopback, serverPort); serverSock = (SSLSocket) serv.accept(); this.buffer = new LinkedBlockingQueue<>(); this.allBytesReceived = allBytesReceived; @@ -107,6 +108,7 @@ private static class SSLLoopbackSubscriber implements FlowTube { } public void start() { + System.out.println("Starting: server listening at: " + serverSock.getLocalSocketAddress()); thread1.start(); thread2.start(); thread3.start(); @@ -144,6 +146,7 @@ private void clientReader() { publisher.submit(List.of(bb)); } } catch (Throwable e) { + System.out.println("clientReader got exception: " + e); e.printStackTrace(); Utils.close(clientSock); } @@ -176,6 +179,7 @@ private void clientWriter() { clientSubscription.request(1); } } catch (Throwable e) { + System.out.println("clientWriter got exception: " + e); e.printStackTrace(); } } @@ -212,6 +216,7 @@ private void serverLoopback() { is.close(); os.close(); serverSock.close(); + System.out.println("serverLoopback exiting normally"); return; } os.write(bb, 0, n); @@ -219,7 +224,10 @@ private void serverLoopback() { loopCount.addAndGet(n); } } catch (Throwable e) { + System.out.println("serverLoopback got exception: " + e); e.printStackTrace(); + } finally { + System.out.println("serverLoopback exiting at count: " + loopCount.get()); } } diff --git a/test/jdk/javax/swing/JPopupMenu/MouseDragPopupTest.java b/test/jdk/javax/swing/JPopupMenu/MouseDragPopupTest.java new file mode 100644 index 0000000000000..e34b53d0360f2 --- /dev/null +++ b/test/jdk/javax/swing/JPopupMenu/MouseDragPopupTest.java @@ -0,0 +1,129 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import java.awt.Dimension; +import java.awt.Point; +import java.awt.Robot; +import java.awt.event.InputEvent; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JPopupMenu; +import javax.swing.SwingUtilities; +import javax.swing.event.PopupMenuEvent; +import javax.swing.event.PopupMenuListener; + +/* + * @test + * @bug 8315655 + * @summary Verifies Right click and dragging over a component with a popup menu will not open the popup + * @key headful + * @run main MouseDragPopupTest + */ +public class MouseDragPopupTest { + static JFrame frame; + static JPanel panel; + static Robot robot; + static volatile boolean failed; + static volatile Point srcPoint; + static volatile Dimension d; + + public static void main(String[] args) throws Exception { + try { + robot = new Robot(); + robot.setAutoWaitForIdle(true); + robot.setAutoDelay(100); + + SwingUtilities.invokeAndWait(() -> { + createAndShowGUI(); + }); + robot.delay(1000); + + SwingUtilities.invokeAndWait(() -> { + srcPoint = frame.getLocationOnScreen(); + d = frame.getSize(); + }); + srcPoint.translate(2 * d.width / 3, 3 * d.height / 4); + + final Point dstPoint = new Point(srcPoint); + dstPoint.translate(4 * d.width / 15, 0); + + robot.mouseMove(srcPoint.x, srcPoint.y); + + robot.mousePress(InputEvent.BUTTON3_DOWN_MASK); + + while (!srcPoint.equals(dstPoint)) { + srcPoint.translate(sign(dstPoint.x - srcPoint.x), 0); + robot.mouseMove(srcPoint.x, srcPoint.y); + } + + if (failed) { + throw new RuntimeException("Popup was shown, Test Failed."); + } + } finally { + robot.mouseRelease(InputEvent.BUTTON3_DOWN_MASK); + SwingUtilities.invokeAndWait(() -> { + if (frame != null) { + frame.dispose(); + } + }); + } + } + + public static int sign(int n) { + return n < 0 ? -1 : n == 0 ? 0 : 1; + } + + static void createAndShowGUI() { + frame = new JFrame("MouseDragPopupTest"); + panel = new JPanel(); + JPanel innerPanel = new JPanel(); + JPopupMenu menu = new JPopupMenu(); + + menu.addPopupMenuListener(new PopupMenuListener() { + @Override + public void popupMenuWillBecomeVisible(PopupMenuEvent e) { + failed = true; + } + + @Override + public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {} + + @Override + public void popupMenuCanceled(PopupMenuEvent e) {} + }); + + menu.add("This should not appear"); + innerPanel.setComponentPopupMenu(menu); + + panel.add(new JLabel("Right click and drag from here")); + panel.add(innerPanel); + panel.add(new JLabel("to here")); + + frame.add(panel); + frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + frame.pack(); + frame.setLocationRelativeTo(null); + frame.setVisible(true); + } +} diff --git a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/WindowsHelper.java b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/WindowsHelper.java index 3faabcd6f8d2c..1a4dbd22897bf 100644 --- a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/WindowsHelper.java +++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/WindowsHelper.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -139,6 +139,30 @@ static PackageHandlers createMsiPackageHandlers() { String.format("TARGETDIR=\"%s\"", unpackDir.toAbsolutePath().normalize()))))); runMsiexecWithRetries(Executor.of("cmd", "/c", unpackBat.toString())); + + // + // WiX3 uses "." as the value of "DefaultDir" field for "ProgramFiles64Folder" folder in msi's Directory table + // WiX4 uses "PFiles64" as the value of "DefaultDir" field for "ProgramFiles64Folder" folder in msi's Directory table + // msiexec creates "Program Files/./" from WiX3 msi which translates to "Program Files/" + // msiexec creates "Program Files/PFiles64/" from WiX4 msi + // So for WiX4 msi we need to transform "Program Files/PFiles64/" into "Program Files/" + // + // WiX4 does the same thing for %LocalAppData%. + // + for (var extraPathComponent : List.of("PFiles64", "LocalApp")) { + if (Files.isDirectory(unpackDir.resolve(extraPathComponent))) { + Path installationSubDirectory = getInstallationSubDirectory(cmd); + Path from = Path.of(extraPathComponent).resolve(installationSubDirectory); + Path to = installationSubDirectory; + TKit.trace(String.format("Convert [%s] into [%s] in [%s] directory", from, to, + unpackDir)); + ThrowingRunnable.toRunnable(() -> { + Files.createDirectories(unpackDir.resolve(to).getParent()); + Files.move(unpackDir.resolve(from), unpackDir.resolve(to)); + TKit.deleteDirectoryRecursive(unpackDir.resolve(extraPathComponent)); + }).run(); + } + } return destinationDir; }; return msi; diff --git a/test/jdk/tools/jpackage/windows/WinL10nTest.java b/test/jdk/tools/jpackage/windows/WinL10nTest.java index deb6b1a8705d1..a868fd5f051c4 100644 --- a/test/jdk/tools/jpackage/windows/WinL10nTest.java +++ b/test/jdk/tools/jpackage/windows/WinL10nTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -33,6 +33,7 @@ import java.util.Arrays; import java.util.List; import java.util.function.Predicate; +import java.util.stream.Collectors; import java.util.stream.Stream; import jdk.jpackage.test.Executor; @@ -55,11 +56,11 @@ public class WinL10nTest { public WinL10nTest(WixFileInitializer wxlFileInitializers[], - String expectedCulture, String expectedErrorMessage, + String[] expectedCultures, String expectedErrorMessage, String userLanguage, String userCountry, boolean enableWixUIExtension) { this.wxlFileInitializers = wxlFileInitializers; - this.expectedCulture = expectedCulture; + this.expectedCultures = expectedCultures; this.expectedErrorMessage = expectedErrorMessage; this.userLanguage = userLanguage; this.userCountry = userCountry; @@ -69,56 +70,65 @@ public WinL10nTest(WixFileInitializer wxlFileInitializers[], @Parameters public static List data() { return List.of(new Object[][]{ - {null, "en-us", null, null, null, false}, - {null, "en-us", null, "en", "US", false}, - {null, "en-us", null, "en", "US", true}, - {null, "de-de", null, "de", "DE", false}, - {null, "de-de", null, "de", "DE", true}, - {null, "ja-jp", null, "ja", "JP", false}, - {null, "ja-jp", null, "ja", "JP", true}, - {null, "zh-cn", null, "zh", "CN", false}, - {null, "zh-cn", null, "zh", "CN", true}, + {null, new String[] {"en-us"}, null, null, null, false}, + {null, new String[] {"en-us"}, null, "en", "US", false}, + {null, new String[] {"en-us"}, null, "en", "US", true}, + {null, new String[] {"de-de"}, null, "de", "DE", false}, + {null, new String[] {"de-de"}, null, "de", "DE", true}, + {null, new String[] {"ja-jp"}, null, "ja", "JP", false}, + {null, new String[] {"ja-jp"}, null, "ja", "JP", true}, + {null, new String[] {"zh-cn"}, null, "zh", "CN", false}, + {null, new String[] {"zh-cn"}, null, "zh", "CN", true}, {new WixFileInitializer[] { WixFileInitializer.create("a.wxl", "en-us") - }, "en-us", null, null, null, false}, + }, new String[] {"en-us"}, null, null, null, false}, {new WixFileInitializer[] { WixFileInitializer.create("a.wxl", "fr") - }, "fr;en-us", null, null, null, false}, + }, new String[] {"fr", "en-us"}, null, null, null, false}, {new WixFileInitializer[] { WixFileInitializer.create("a.wxl", "fr"), WixFileInitializer.create("b.wxl", "fr") - }, "fr;en-us", null, null, null, false}, + }, new String[] {"fr", "en-us"}, null, null, null, false}, {new WixFileInitializer[] { WixFileInitializer.create("a.wxl", "it"), WixFileInitializer.create("b.wxl", "fr") - }, "it;fr;en-us", null, null, null, false}, + }, new String[] {"it", "fr", "en-us"}, null, null, null, false}, {new WixFileInitializer[] { WixFileInitializer.create("c.wxl", "it"), WixFileInitializer.create("b.wxl", "fr") - }, "fr;it;en-us", null, null, null, false}, + }, new String[] {"fr", "it", "en-us"}, null, null, null, false}, {new WixFileInitializer[] { WixFileInitializer.create("a.wxl", "fr"), WixFileInitializer.create("b.wxl", "it"), WixFileInitializer.create("c.wxl", "fr"), WixFileInitializer.create("d.wxl", "it") - }, "fr;it;en-us", null, null, null, false}, + }, new String[] {"fr", "it", "en-us"}, null, null, null, false}, {new WixFileInitializer[] { WixFileInitializer.create("c.wxl", "it"), WixFileInitializer.createMalformed("b.wxl") }, null, null, null, null, false}, {new WixFileInitializer[] { WixFileInitializer.create("MsiInstallerStrings_de.wxl", "de") - }, "en-us", null, null, null, false} + }, new String[] {"en-us"}, null, null, null, false} }); } - private static Stream getLightCommandLine( - Executor.Result result) { - return result.getOutput().stream().filter(s -> { + private static Stream getBuildCommandLine(Executor.Result result) { + return result.getOutput().stream().filter(createToolCommandLinePredicate("light").or( + createToolCommandLinePredicate("wix"))); + } + + private static boolean isWix3(Executor.Result result) { + return result.getOutput().stream().anyMatch(createToolCommandLinePredicate("light")); + } + + private final static Predicate createToolCommandLinePredicate(String wixToolName) { + var toolFileName = wixToolName + ".exe"; + return (s) -> { s = s.trim(); - return s.startsWith("light.exe") || ((s.contains("\\light.exe ") - && s.contains(" -out "))); - }); + return s.startsWith(toolFileName) || ((s.contains(String.format("\\%s ", toolFileName)) && s. + contains(" -out "))); + }; } private static List createDefaultL10nFilesLocVerifiers(Path tempDir) { @@ -148,14 +158,23 @@ public void test() throws IOException { // 2. Instruct test to save jpackage output. cmd.setFakeRuntime().saveConsoleOutput(true); + boolean withJavaOptions = false; + // Set JVM default locale that is used to select primary l10n file. if (userLanguage != null) { + withJavaOptions = true; cmd.addArguments("-J-Duser.language=" + userLanguage); } if (userCountry != null) { + withJavaOptions = true; cmd.addArguments("-J-Duser.country=" + userCountry); } + if (withJavaOptions) { + // Use jpackage as a command to allow "-J" options come through + cmd.useToolProvider(false); + } + // Cultures handling is affected by the WiX extensions used. // By default only WixUtilExtension is used, this flag // additionally enables WixUIExtension. @@ -169,9 +188,16 @@ public void test() throws IOException { cmd.addArguments("--temp", tempDir.toString()); }) .addBundleVerifier((cmd, result) -> { - if (expectedCulture != null) { - TKit.assertTextStream("-cultures:" + expectedCulture).apply( - getLightCommandLine(result)); + if (expectedCultures != null) { + String expected; + if (isWix3(result)) { + expected = "-cultures:" + String.join(";", expectedCultures); + } else { + expected = Stream.of(expectedCultures).map(culture -> { + return String.join(" ", "-culture", culture); + }).collect(Collectors.joining(" ")); + } + TKit.assertTextStream(expected).apply(getBuildCommandLine(result)); } if (expectedErrorMessage != null) { @@ -180,24 +206,24 @@ public void test() throws IOException { } if (wxlFileInitializers != null) { + var wixSrcDir = Path.of(cmd.getArgumentValue("--temp")).resolve("config"); + if (allWxlFilesValid) { for (var v : wxlFileInitializers) { if (!v.name.startsWith("MsiInstallerStrings_")) { - v.createCmdOutputVerifier(resourceDir).apply( - getLightCommandLine(result)); + v.createCmdOutputVerifier(wixSrcDir).apply(getBuildCommandLine(result)); } } Path tempDir = getTempDirectory(cmd, tempRoot).toAbsolutePath(); for (var v : createDefaultL10nFilesLocVerifiers(tempDir)) { - v.apply(getLightCommandLine(result)); + v.apply(getBuildCommandLine(result)); } } else { Stream.of(wxlFileInitializers) .filter(Predicate.not(WixFileInitializer::isValid)) .forEach(v -> v.createCmdOutputVerifier( - resourceDir).apply(result.getOutput().stream())); - TKit.assertFalse( - getLightCommandLine(result).findAny().isPresent(), + wixSrcDir).apply(result.getOutput().stream())); + TKit.assertFalse(getBuildCommandLine(result).findAny().isPresent(), "Check light.exe was not invoked"); } } @@ -223,7 +249,7 @@ public void test() throws IOException { } final private WixFileInitializer[] wxlFileInitializers; - final private String expectedCulture; + final private String[] expectedCultures; final private String expectedErrorMessage; final private String userLanguage; final private String userCountry; diff --git a/test/micro/org/openjdk/bench/vm/compiler/PopCount.java b/test/micro/org/openjdk/bench/vm/compiler/PopCount.java new file mode 100644 index 0000000000000..cbf44023c1e42 --- /dev/null +++ b/test/micro/org/openjdk/bench/vm/compiler/PopCount.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2024 IBM Corporation. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package org.openjdk.bench.vm.compiler; + +import org.openjdk.jmh.annotations.*; + +import java.util.concurrent.TimeUnit; + +@BenchmarkMode(Mode.AverageTime) +@OutputTimeUnit(TimeUnit.MILLISECONDS) +@State(Scope.Thread) +@Warmup(iterations = 10, time = 1) +@Measurement(iterations = 5, time = 1) +@Fork(value = 5) +public class PopCount { + int numTests = 100_000; + + @Benchmark + public long test() { + long l1 = 1, l2 = 2, l3 = 3, l4 = 4, l5 = 5, l6 = 6, l7 = 7, l8 = 9, l9 = 9, l10 = 10; + for (long i = 0; i < numTests; i++) { + l1 ^= Long.bitCount(l1) + i; + l2 ^= Long.bitCount(l2) + i; + l3 ^= Long.bitCount(l3) + i; + l4 ^= Long.bitCount(l4) + i; + l5 ^= Long.bitCount(l5) + i; + l6 ^= Long.bitCount(l6) + i; + l7 ^= Long.bitCount(l7) + i; + l8 ^= Long.bitCount(l8) + i; + l9 ^= Long.bitCount(l9) + i; + l10 ^= Long.bitCount(l10) + i; + } + return l1 + l2 + l3 + l4 + l5 + l6 + l7 + l8 + l9 + l10; + } + +}